Beginning to work on new push notifications setup

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2020-02-12 21:09:38 +01:00
parent 59c9106ff8
commit d75e84c1fa
No known key found for this signature in database
GPG Key ID: CDE0BBD2738C4CC0
8 changed files with 595 additions and 133 deletions

View File

@ -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"
}

View File

@ -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() {
}
})*/
}
}

View File

@ -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
}
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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
}

View File

@ -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,

View File

@ -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"
}