mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-21 20:49:36 +01:00
Beginning to work on new push notifications setup
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
59c9106ff8
commit
d75e84c1fa
@ -19,6 +19,6 @@
|
||||
*/
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.work:work-gcm:2.3.0-beta02"
|
||||
implementation "androidx.work:work-gcm:2.3.1"
|
||||
implementation "com.google.firebase:firebase-messaging:20.1.0"
|
||||
}
|
||||
|
@ -20,19 +20,71 @@
|
||||
package com.nextcloud.talk.services.firebase
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.nextcloud.talk.jobs.NotificationWorker
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.media.AudioAttributes
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.emoji.text.EmojiCompat
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import com.bluelinelabs.logansquare.LoganSquare
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.MagicCallActivity
|
||||
import com.nextcloud.talk.jobs.MessageNotificationWorker
|
||||
import com.nextcloud.talk.jobs.NotificationWorker
|
||||
import com.nextcloud.talk.models.SignatureVerification
|
||||
import com.nextcloud.talk.models.json.push.DecryptedPushMessage
|
||||
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
||||
import com.nextcloud.talk.utils.NotificationUtils
|
||||
import com.nextcloud.talk.utils.NotificationUtils.cancelAllNotificationsForAccount
|
||||
import com.nextcloud.talk.utils.NotificationUtils.cancelExistingNotificationWithId
|
||||
import com.nextcloud.talk.utils.PushUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FROM_NOTIFICATION_START_CALL
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import okhttp3.JavaNetCookieJar
|
||||
import okhttp3.OkHttpClient
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import retrofit2.Retrofit
|
||||
import java.net.CookieManager
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.PrivateKey
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
|
||||
class MagicFirebaseMessagingService : FirebaseMessagingService(), KoinComponent {
|
||||
val appPreferences: AppPreferences by inject()
|
||||
val retrofit: Retrofit by inject()
|
||||
val okHttpClient: OkHttpClient by inject()
|
||||
val eventBus: EventBus by inject()
|
||||
val usersRepository: UsersRepository by inject()
|
||||
|
||||
private var isServiceInForeground: Boolean = false
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
eventBus.register(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
eventBus.unregister(this)
|
||||
isServiceInForeground = false
|
||||
}
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
super.onNewToken(token)
|
||||
@ -42,15 +94,143 @@ class MagicFirebaseMessagingService : FirebaseMessagingService(), KoinComponent
|
||||
@SuppressLint("LongLogTag")
|
||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||
remoteMessage.data.let {
|
||||
val messageData: Data = Data.Builder()
|
||||
.putString(BundleKeys.KEY_NOTIFICATION_SUBJECT, it["subject"])
|
||||
.putString(BundleKeys.KEY_NOTIFICATION_SIGNATURE, it["signature"])
|
||||
.build()
|
||||
val pushNotificationWork: OneTimeWorkRequest = OneTimeWorkRequest.Builder(NotificationWorker::class.java)
|
||||
.setInputData(messageData)
|
||||
.build()
|
||||
WorkManager.getInstance().enqueue(pushNotificationWork)
|
||||
|
||||
decryptMessage(it["subject"]!!, it["signature"]!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun decryptMessage(subject: String, signature: String) {
|
||||
val signatureVerification: SignatureVerification
|
||||
val decryptedPushMessage: DecryptedPushMessage
|
||||
|
||||
try {
|
||||
val base64DecodedSubject = Base64.decode(subject, Base64.DEFAULT)
|
||||
val base64DecodedSignature = Base64.decode(signature, Base64.DEFAULT)
|
||||
val pushUtils = PushUtils(usersRepository)
|
||||
val privateKey = pushUtils.readKeyFromFile(false) as PrivateKey
|
||||
try {
|
||||
signatureVerification = pushUtils.verifySignature(base64DecodedSignature, base64DecodedSubject)
|
||||
if (signatureVerification.signatureValid) {
|
||||
val cipher = Cipher.getInstance("RSA/None/PKCS1Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey)
|
||||
val decryptedSubject = cipher.doFinal(base64DecodedSubject)
|
||||
decryptedPushMessage = LoganSquare.parse(String(decryptedSubject), DecryptedPushMessage::class.java)
|
||||
decryptedPushMessage.apply {
|
||||
val timestamp = decryptedPushMessage.timestamp
|
||||
when {
|
||||
delete -> {
|
||||
cancelExistingNotificationWithId(applicationContext, signatureVerification.userEntity, notificationId!!)
|
||||
}
|
||||
deleteAll -> {
|
||||
cancelAllNotificationsForAccount(applicationContext, signatureVerification.userEntity)
|
||||
}
|
||||
type == "call" -> {
|
||||
val fullScreenIntent = Intent(applicationContext, MagicCallActivity::class.java)
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_ROOM_ID, decryptedPushMessage.id)
|
||||
bundle.putParcelable(KEY_USER_ENTITY, signatureVerification.userEntity)
|
||||
bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, true)
|
||||
fullScreenIntent.putExtras(bundle)
|
||||
|
||||
fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
val fullScreenPendingIntent = PendingIntent.getActivity(this@MagicFirebaseMessagingService, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val audioAttributesBuilder = AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
audioAttributesBuilder.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST)
|
||||
|
||||
val soundUri = NotificationUtils.getCallSoundUri(applicationContext, appPreferences)
|
||||
val vibrationEffect = NotificationUtils.getVibrationEffect(appPreferences)
|
||||
|
||||
val notificationChannelId = NotificationUtils.getNotificationChannelId(applicationContext, applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_calls), applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_calls_description), true,
|
||||
NotificationManagerCompat.IMPORTANCE_HIGH, soundUri!!,
|
||||
audioAttributesBuilder.build(), vibrationEffect, false, null)
|
||||
|
||||
val userBaseUrl = Uri.parse(signatureVerification.userEntity.baseUrl).toString()
|
||||
|
||||
val notificationBuilder = NotificationCompat.Builder(this@MagicFirebaseMessagingService, notificationChannelId)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
.setSmallIcon(R.drawable.ic_call_black_24dp)
|
||||
.setSubText(userBaseUrl)
|
||||
.setShowWhen(true)
|
||||
.setWhen(decryptedPushMessage.timestamp)
|
||||
.setContentTitle(EmojiCompat.get().process(decryptedPushMessage.subject.toString()))
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(true)
|
||||
//.setTimeoutAfter(45000L)
|
||||
.setFullScreenIntent(fullScreenPendingIntent, true)
|
||||
.setSound(NotificationUtils.getCallSoundUri(applicationContext, appPreferences))
|
||||
|
||||
if (vibrationEffect != null) {
|
||||
notificationBuilder.setVibrate(vibrationEffect)
|
||||
}
|
||||
|
||||
val notification = notificationBuilder.build()
|
||||
notification.flags = notification.flags or Notification.FLAG_INSISTENT
|
||||
isServiceInForeground = true
|
||||
checkIfCallIsActive(signatureVerification, decryptedPushMessage)
|
||||
startForeground(decryptedPushMessage.timestamp.toInt(), notification)
|
||||
}
|
||||
else -> {
|
||||
val messageData = Data.Builder()
|
||||
.putString(BundleKeys.KEY_DECRYPTED_PUSH_MESSAGE, LoganSquare.serialize(decryptedPushMessage))
|
||||
.putString(BundleKeys.KEY_SIGNATURE_VERIFICATION, LoganSquare.serialize(signatureVerification))
|
||||
.build()
|
||||
val pushNotificationWork = OneTimeWorkRequest.Builder(MessageNotificationWorker::class.java).setInputData(messageData).build()
|
||||
WorkManager.getInstance().enqueue(pushNotificationWork)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e1: NoSuchAlgorithmException) {
|
||||
Log.d(NotificationWorker.TAG, "No proper algorithm to decrypt the message " + e1.localizedMessage)
|
||||
} catch (e1: NoSuchPaddingException) {
|
||||
Log.d(NotificationWorker.TAG, "No proper padding to decrypt the message " + e1.localizedMessage)
|
||||
} catch (e1: InvalidKeyException) {
|
||||
Log.d(NotificationWorker.TAG, "Invalid private key " + e1.localizedMessage)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.d(NotificationWorker.TAG, "Something went very wrong " + exception.localizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfCallIsActive(signatureVerification: SignatureVerification, decryptedPushMessage: DecryptedPushMessage) {
|
||||
/*val ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build().create(NcApi::class.java)
|
||||
var hasParticipantsInCall = false
|
||||
var inCallOnDifferentDevice = false
|
||||
|
||||
ncApi.getPeersForCall(ApiUtils.getCredentials(signatureVerification.userEntity.username, signatureVerification.userEntity.token),
|
||||
ApiUtils.getUrlForParticipants(signatureVerification.userEntity.baseUrl,
|
||||
decryptedPushMessage.id))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(object : Observer<ParticipantsOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
}
|
||||
|
||||
override fun onNext(participantsOverall: ParticipantsOverall) {
|
||||
val participantList: List<Participant> = participantsOverall.ocs.data
|
||||
for (participant in participantList) {
|
||||
if (participant.participantFlags != Participant.ParticipantFlags.NOT_IN_CALL) {
|
||||
hasParticipantsInCall = true
|
||||
if (participant.userId == signatureVerification.userEntity.userId) {
|
||||
inCallOnDifferentDevice = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasParticipantsInCall || inCallOnDifferentDevice) {
|
||||
stopForeground(true)
|
||||
} else if (isServiceInForeground) {
|
||||
checkIfCallIsActive(signatureVerification, decryptedPushMessage)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {}
|
||||
override fun onComplete() {
|
||||
}
|
||||
})*/
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2020 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* 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.nextcloud.talk.jobs
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.media.AudioAttributes
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.Person
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.emoji.text.EmojiCompat
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import coil.Coil
|
||||
import coil.target.Target
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.bluelinelabs.logansquare.LoganSquare
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
import com.nextcloud.talk.models.SignatureVerification
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import com.nextcloud.talk.models.json.push.DecryptedPushMessage
|
||||
import com.nextcloud.talk.newarch.utils.Images
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.NotificationUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import java.util.function.Consumer
|
||||
|
||||
class MessageNotificationWorker(
|
||||
context: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : CoroutineWorker(context, workerParams), KoinComponent {
|
||||
val appPreferences: AppPreferences by inject()
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val data = inputData
|
||||
val decryptedPushMessageString: String = data.getString(BundleKeys.KEY_DECRYPTED_PUSH_MESSAGE)!!
|
||||
val signatureVerificationString: String = data.getString(BundleKeys.KEY_SIGNATURE_VERIFICATION)!!
|
||||
|
||||
val decryptedPushMessage = LoganSquare.parse(decryptedPushMessageString, DecryptedPushMessage::class.java)
|
||||
val signatureVerification = LoganSquare.parse(signatureVerificationString, SignatureVerification::class.java)
|
||||
|
||||
// we support Nextcloud Talk 4.0 and up so assuming "no-ping" capability here
|
||||
val intent = Intent(applicationContext, MainActivity::class.java)
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, signatureVerification.userEntity)
|
||||
bundle.putString(BundleKeys.KEY_CONVERSATION_TOKEN, decryptedPushMessage.id)
|
||||
intent.putExtras(bundle)
|
||||
|
||||
when (decryptedPushMessage.type) {
|
||||
"room" -> {
|
||||
showNotificationWithObjectData(decryptedPushMessage, signatureVerification, intent)
|
||||
}
|
||||
"chat" -> {
|
||||
if (decryptedPushMessage.notificationId != null) {
|
||||
showNotificationWithObjectData(decryptedPushMessage, signatureVerification, intent)
|
||||
} else {
|
||||
showNotification(decryptedPushMessage, signatureVerification, null, intent)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
private fun showNotificationWithObjectData(decryptedPushMessage: DecryptedPushMessage, signatureVerification: SignatureVerification, intent: Intent) {
|
||||
|
||||
}
|
||||
|
||||
private fun showNotification(
|
||||
decryptedPushMessage: DecryptedPushMessage,
|
||||
signatureVerification: SignatureVerification,
|
||||
conversationType: Conversation.ConversationType?, intent: Intent) {
|
||||
val largeIcon = when (conversationType) {
|
||||
Conversation.ConversationType.PUBLIC_CONVERSATION -> {
|
||||
BitmapFactory.decodeResource(applicationContext.resources, R.drawable.ic_link_black_24px)
|
||||
}
|
||||
Conversation.ConversationType.GROUP_CONVERSATION -> {
|
||||
BitmapFactory.decodeResource(applicationContext.resources, R.drawable.ic_people_group_black_24px)
|
||||
}
|
||||
else -> {
|
||||
// one to one and unknown
|
||||
BitmapFactory.decodeResource(applicationContext.resources, R.drawable.ic_chat_black_24dp)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
val adjustedConversationType = conversationType ?: Conversation.ConversationType.ONE_TO_ONE_CONVERSATION
|
||||
|
||||
val pendingIntent: PendingIntent? = PendingIntent.getActivity(applicationContext,
|
||||
0, intent, 0)
|
||||
|
||||
val userBaseUrl = Uri.parse(signatureVerification.userEntity.baseUrl).toString()
|
||||
val soundUri = NotificationUtils.getMessageSoundUri(applicationContext, appPreferences)
|
||||
|
||||
val audioAttributesBuilder: AudioAttributes.Builder =
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
|
||||
|
||||
val vibrationEffect = NotificationUtils.getVibrationEffect(appPreferences)
|
||||
|
||||
val notificationChannelId = NotificationUtils.getNotificationChannelId(applicationContext, applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_messages), applicationContext.resources
|
||||
.getString(R.string.nc_notification_channel_messages), true,
|
||||
NotificationManagerCompat.IMPORTANCE_HIGH, soundUri!!,
|
||||
audioAttributesBuilder.build(), vibrationEffect, false, null)
|
||||
|
||||
val notificationBuilder = NotificationCompat.Builder(applicationContext, notificationChannelId)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(Notification.CATEGORY_MESSAGE)
|
||||
.setSmallIcon(R.drawable.ic_logo)
|
||||
.setLargeIcon(largeIcon)
|
||||
.setSubText(userBaseUrl)
|
||||
.setShowWhen(true)
|
||||
.setWhen(decryptedPushMessage.timestamp)
|
||||
.setContentTitle(EmojiCompat.get().process(decryptedPushMessage.subject.toString()))
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setSound(soundUri)
|
||||
|
||||
if (vibrationEffect != null) {
|
||||
notificationBuilder.setVibrate(vibrationEffect)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
// This method should exist since API 21, but some phones don't have it
|
||||
// So as a safeguard, we don't use it until 23
|
||||
notificationBuilder.color = applicationContext.resources.getColor(R.color.colorPrimary)
|
||||
}
|
||||
|
||||
var notificationId = decryptedPushMessage.timestamp.toInt()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && decryptedPushMessage.notificationUser != null && decryptedPushMessage.type == "chat") {
|
||||
var style: NotificationCompat.MessagingStyle? = null
|
||||
|
||||
decryptedPushMessage.id?.let { decryptedMessageId ->
|
||||
val activeStatusBarNotification =
|
||||
NotificationUtils.findNotificationForRoom(
|
||||
applicationContext,
|
||||
signatureVerification.userEntity, decryptedMessageId)
|
||||
activeStatusBarNotification?.let { activeStatusBarNotification ->
|
||||
notificationId = activeStatusBarNotification.id
|
||||
style = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(activeStatusBarNotification.notification)
|
||||
notificationBuilder.setStyle(style)
|
||||
}
|
||||
}
|
||||
|
||||
notificationBuilder.setOnlyAlertOnce(true)
|
||||
decryptedPushMessage.notificationUser?.let { notificationUser ->
|
||||
val person = Person.Builder().setKey(signatureVerification.userEntity.id.toString() + "@" + notificationUser.id)
|
||||
.setName(EmojiCompat.get().process(notificationUser.name))
|
||||
.setBot(notificationUser.type == "bot")
|
||||
|
||||
if (notificationUser.type == "user" || notificationUser.type == "guest") {
|
||||
val avatarUrl = when (notificationUser.type) {
|
||||
"user" -> {
|
||||
ApiUtils.getUrlForAvatarWithName(signatureVerification.userEntity.baseUrl, notificationUser.id, R.dimen.avatar_size)
|
||||
}
|
||||
else -> {
|
||||
ApiUtils.getUrlForAvatarWithNameForGuests(signatureVerification.userEntity.baseUrl, notificationUser.name, R.dimen.avatar_size)
|
||||
}
|
||||
}
|
||||
|
||||
val target = object : Target {
|
||||
override fun onSuccess(result: Drawable) {
|
||||
super.onSuccess(result)
|
||||
person.setIcon(IconCompat.createWithBitmap(result.toBitmap()))
|
||||
notificationBuilder.setStyle(getStyle(decryptedPushMessage, adjustedConversationType, person.build(), style))
|
||||
NotificationManagerCompat.from(applicationContext).notify(notificationId, notificationBuilder.build())
|
||||
}
|
||||
|
||||
override fun onError(error: Drawable?) {
|
||||
super.onError(error)
|
||||
notificationBuilder.setStyle(getStyle(decryptedPushMessage, adjustedConversationType, person.build(), style))
|
||||
NotificationManagerCompat.from(applicationContext).notify(notificationId, notificationBuilder.build())
|
||||
}
|
||||
}
|
||||
|
||||
val request = Images().getRequestForUrl(
|
||||
Coil.loader(), applicationContext, avatarUrl!!, signatureVerification.userEntity,
|
||||
target, null, CircleCropTransformation()
|
||||
)
|
||||
|
||||
Coil.loader().load(request)
|
||||
} else {
|
||||
notificationBuilder.setStyle(getStyle(decryptedPushMessage, adjustedConversationType, person.build(), style))
|
||||
NotificationManagerCompat.from(applicationContext).notify(notificationId, notificationBuilder.build())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NotificationManagerCompat.from(applicationContext).notify(notificationId, notificationBuilder.build())
|
||||
}
|
||||
}
|
||||
private fun getStyle(
|
||||
decryptedPushMessage: DecryptedPushMessage,
|
||||
conversationType: Conversation.ConversationType,
|
||||
person: Person,
|
||||
style: NotificationCompat.MessagingStyle?
|
||||
): NotificationCompat.MessagingStyle? {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val newStyle = NotificationCompat.MessagingStyle(person)
|
||||
newStyle.conversationTitle = decryptedPushMessage.subject
|
||||
newStyle.isGroupConversation = conversationType != Conversation.ConversationType.ONE_TO_ONE_CONVERSATION
|
||||
style?.messages?.forEach(
|
||||
Consumer { message: NotificationCompat.MessagingStyle.Message ->
|
||||
newStyle.addMessage(
|
||||
NotificationCompat.MessagingStyle.Message(
|
||||
message.text,
|
||||
message.timestamp, message.person
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
newStyle.addMessage(decryptedPushMessage.text, decryptedPushMessage.timestamp, person)
|
||||
return newStyle
|
||||
}
|
||||
|
||||
// we'll never come here
|
||||
return style
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -426,8 +426,7 @@ class NotificationWorker(
|
||||
signatureVerification!!.userEntity, decryptedPushMessage!!.id
|
||||
)
|
||||
val notificationId: Int
|
||||
notificationId = activeStatusBarNotification?.id ?: crc32.value
|
||||
.toInt()
|
||||
notificationId = activeStatusBarNotification?.id ?: crc32.value.toInt()
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.N && decryptedPushMessage!!.notificationUser != null && decryptedPushMessage!!.type == "chat") {
|
||||
var style: MessagingStyle? = null
|
||||
if (activeStatusBarNotification != null) {
|
||||
@ -482,7 +481,6 @@ class NotificationWorker(
|
||||
notificationBuilder.setStyle(getStyle(person.build(), style))
|
||||
sendNotificationWithId(notificationId, notificationBuilder.build())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val request = Images().getRequestForUrl(
|
||||
@ -501,36 +499,6 @@ class NotificationWorker(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStyle(
|
||||
person: Person,
|
||||
style: MessagingStyle?
|
||||
): MessagingStyle? {
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.N) {
|
||||
val newStyle =
|
||||
MessagingStyle(person)
|
||||
newStyle.conversationTitle = decryptedPushMessage!!.subject
|
||||
newStyle.isGroupConversation = conversationType != "one2one"
|
||||
style?.messages?.forEach(
|
||||
Consumer { message: Message ->
|
||||
newStyle.addMessage(
|
||||
Message(
|
||||
message.text,
|
||||
message.timestamp, message.person
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
newStyle.addMessage(
|
||||
decryptedPushMessage!!.text, decryptedPushMessage!!.timestamp,
|
||||
person
|
||||
)
|
||||
return newStyle
|
||||
}
|
||||
|
||||
// we'll never come here
|
||||
|
||||
return style
|
||||
}
|
||||
|
||||
private fun sendNotificationWithId(
|
||||
notificationId: Int,
|
||||
|
@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* 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.nextcloud.talk.models.json.push;
|
||||
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonIgnore;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Parcel
|
||||
@JsonObject
|
||||
public class DecryptedPushMessage {
|
||||
@JsonField(name = "app")
|
||||
public String app;
|
||||
|
||||
@JsonField(name = "type")
|
||||
public String type;
|
||||
|
||||
@JsonField(name = "subject")
|
||||
public String subject;
|
||||
|
||||
@JsonField(name = "id")
|
||||
public String id;
|
||||
|
||||
@JsonField(name = "nid")
|
||||
public long notificationId;
|
||||
|
||||
@JsonField(name = "delete")
|
||||
public boolean delete;
|
||||
|
||||
@JsonField(name = "delete-all")
|
||||
public boolean deleteAll;
|
||||
|
||||
@JsonIgnore
|
||||
public NotificationUser notificationUser;
|
||||
|
||||
@JsonIgnore
|
||||
public String text;
|
||||
|
||||
@JsonIgnore
|
||||
public long timestamp;
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* 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.nextcloud.talk.models.json.push
|
||||
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonIgnore
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import lombok.Data
|
||||
import org.parceler.Parcel
|
||||
|
||||
@Data
|
||||
@Parcel
|
||||
@JsonObject
|
||||
class DecryptedPushMessage {
|
||||
@JvmField
|
||||
@JsonField(name = ["app"])
|
||||
var app: String? = null
|
||||
@JvmField
|
||||
@JsonField(name = ["type"])
|
||||
var type: String? = null
|
||||
@JvmField
|
||||
@JsonField(name = ["subject"])
|
||||
var subject: String? = null
|
||||
@JvmField
|
||||
@JsonField(name = ["id"])
|
||||
var id: String? = null
|
||||
@JvmField
|
||||
@JsonField(name = ["nid"])
|
||||
var notificationId: Long? = null
|
||||
@JvmField
|
||||
@JsonField(name = ["delete"])
|
||||
var delete = false
|
||||
@JvmField
|
||||
@JsonField(name = ["delete-all"])
|
||||
var deleteAll = false
|
||||
@JvmField
|
||||
@JsonIgnore
|
||||
var notificationUser: NotificationUser? = null
|
||||
@JvmField
|
||||
@JsonIgnore
|
||||
var text: String? = null
|
||||
@JvmField
|
||||
@JsonIgnore
|
||||
var timestamp: Long = 0
|
||||
}
|
@ -26,11 +26,21 @@ import android.app.NotificationChannel
|
||||
import android.app.NotificationChannelGroup
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.text.TextUtils
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.bluelinelabs.logansquare.LoganSquare
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.models.RingtoneSettings
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
object NotificationUtils {
|
||||
val NOTIFICATION_CHANNEL_CALLS = "NOTIFICATION_CHANNEL_CALLS"
|
||||
@ -40,38 +50,87 @@ object NotificationUtils {
|
||||
val NOTIFICATION_CHANNEL_MESSAGES_V3 = "NOTIFICATION_CHANNEL_MESSAGES_V3"
|
||||
val NOTIFICATION_CHANNEL_CALLS_V3 = "NOTIFICATION_CHANNEL_CALLS_V3"
|
||||
|
||||
fun getVibrationEffect(appPreferences: AppPreferences): LongArray? {
|
||||
return if (appPreferences.shouldVibrateSetting) {
|
||||
longArrayOf(0L, 400L, 800L, 600L, 800L, 800L, 800L, 1000L)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getCallSoundUri(context: Context, appPreferences: AppPreferences) : Uri? {
|
||||
val ringtonePreferencesString: String? = appPreferences.callRingtoneUri
|
||||
|
||||
return if (TextUtils.isEmpty(ringtonePreferencesString)) {
|
||||
Uri.parse("android.resource://" + context.packageName +
|
||||
"/raw/librem_by_feandesign_call")
|
||||
} else {
|
||||
try {
|
||||
val ringtoneSettings = LoganSquare.parse(ringtonePreferencesString, RingtoneSettings::class.java)
|
||||
ringtoneSettings.ringtoneUri
|
||||
} catch (exception: IOException) {
|
||||
Uri.parse("android.resource://" + context.packageName + "/raw/librem_by_feandesign_call")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessageSoundUri(context: Context, appPreferences: AppPreferences) : Uri? {
|
||||
val ringtonePreferencesString: String? = appPreferences.messageRingtoneUri
|
||||
|
||||
return if (TextUtils.isEmpty(ringtonePreferencesString)) {
|
||||
Uri.parse("android.resource://" + context.packageName + "/raw/librem_by_feandesign_message")
|
||||
} else {
|
||||
try {
|
||||
val ringtoneSettings = LoganSquare.parse(ringtonePreferencesString, RingtoneSettings::class.java)
|
||||
ringtoneSettings.ringtoneUri
|
||||
} catch (exception: IOException) {
|
||||
Uri.parse("android.resource://" + context.packageName + "/raw/librem_by_feandesign_message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotificationChannelId(context: Context, channelName: String,
|
||||
channelDescription: String, enableLights: Boolean,
|
||||
importance: Int, sound: Uri, audioAttributes: AudioAttributes, vibrationPattern: LongArray?, bypassDnd: Boolean, lockScreenVisibility: Integer?): String {
|
||||
val channelId = Objects.hash(channelName, channelDescription, enableLights, importance, sound, audioAttributes, vibrationPattern, bypassDnd, lockScreenVisibility).toString()
|
||||
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
|
||||
createNotificationChannel(context, channelId, channelName, channelDescription, enableLights, importance, sound, audioAttributes, vibrationPattern, bypassDnd, lockScreenVisibility)
|
||||
}
|
||||
|
||||
return channelId
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
fun createNotificationChannel(
|
||||
context: Context,
|
||||
channelId: String,
|
||||
channelName: String,
|
||||
channelDescription: String,
|
||||
enableLights: Boolean,
|
||||
importance: Int
|
||||
) {
|
||||
fun createNotificationChannel(context: Context,
|
||||
channelId: String, channelName: String,
|
||||
channelDescription: String, enableLights: Boolean,
|
||||
importance: Int, sound: Uri, audioAttributes: AudioAttributes,
|
||||
vibrationPattern: LongArray?, bypassDnd: Boolean = false, lockScreenVisibility: Integer?) {
|
||||
|
||||
val notificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val notificationManagerCompat = NotificationManagerCompat.from(context)
|
||||
if (notificationManagerCompat.getNotificationChannel(channelId) == null) {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O && notificationManager.getNotificationChannel(
|
||||
channelId
|
||||
) == null
|
||||
) {
|
||||
|
||||
val channel = NotificationChannel(
|
||||
channelId, channelName,
|
||||
importance
|
||||
)
|
||||
val channel = NotificationChannel(channelId, channelName, importance)
|
||||
|
||||
channel.description = channelDescription
|
||||
channel.enableLights(enableLights)
|
||||
channel.lightColor = R.color.colorPrimary
|
||||
channel.setSound(null, null)
|
||||
channel.setSound(sound, audioAttributes)
|
||||
if (vibrationPattern != null) {
|
||||
channel.enableVibration(true)
|
||||
channel.vibrationPattern = vibrationPattern
|
||||
} else {
|
||||
channel.enableVibration(false)
|
||||
}
|
||||
channel.setBypassDnd(bypassDnd)
|
||||
if (lockScreenVisibility != null) {
|
||||
channel.lockscreenVisibility = lockScreenVisibility
|
||||
}
|
||||
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
notificationManagerCompat.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
fun createNotificationChannelGroup(
|
||||
context: Context,
|
||||
|
@ -62,4 +62,7 @@ object BundleKeys {
|
||||
val KEY_FILE_ID = "KEY_FILE_ID"
|
||||
val KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID"
|
||||
val KEY_CONVERSATION_ID = "KEY_CONVERSATION_ID"
|
||||
|
||||
val KEY_DECRYPTED_PUSH_MESSAGE = "KEY_DECRYPTED_PUSH_MESSAGE"
|
||||
val KEY_SIGNATURE_VERIFICATION = "KEY_SIGNATURE_VERIFICATION"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user