From d75e84c1fabcf1ece9eb863cbb744db12ac3dc87 Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Wed, 12 Feb 2020 21:09:38 +0100 Subject: [PATCH] Beginning to work on new push notifications setup Signed-off-by: Mario Danic --- app/gplay.gradle | 2 +- .../firebase/MagicFirebaseMessagingService.kt | 206 +++++++++++++- .../talk/jobs/MessageNotificationWorker.kt | 254 ++++++++++++++++++ .../nextcloud/talk/jobs/NotificationWorker.kt | 34 +-- .../json/push/DecryptedPushMessage.java | 64 ----- .../models/json/push/DecryptedPushMessage.kt | 62 +++++ .../nextcloud/talk/utils/NotificationUtils.kt | 103 +++++-- .../nextcloud/talk/utils/bundle/BundleKeys.kt | 3 + 8 files changed, 595 insertions(+), 133 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/jobs/MessageNotificationWorker.kt delete mode 100644 app/src/main/java/com/nextcloud/talk/models/json/push/DecryptedPushMessage.java create mode 100644 app/src/main/java/com/nextcloud/talk/models/json/push/DecryptedPushMessage.kt diff --git a/app/gplay.gradle b/app/gplay.gradle index a613566ec..58b0bc12c 100644 --- a/app/gplay.gradle +++ b/app/gplay.gradle @@ -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" } diff --git a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt index 2b7498d5d..fa1bf80a2 100644 --- a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt +++ b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt @@ -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 { + override fun onSubscribe(d: Disposable) { + } + + override fun onNext(participantsOverall: ParticipantsOverall) { + val participantList: List = 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() { + } + })*/ + + } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/jobs/MessageNotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/MessageNotificationWorker.kt new file mode 100644 index 000000000..0d7178e89 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/jobs/MessageNotificationWorker.kt @@ -0,0 +1,254 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2020 Mario Danic + * + * 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 . + */ +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 + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index fbf85f17c..352c0c6bf 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -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, diff --git a/app/src/main/java/com/nextcloud/talk/models/json/push/DecryptedPushMessage.java b/app/src/main/java/com/nextcloud/talk/models/json/push/DecryptedPushMessage.java deleted file mode 100644 index 0629c02cc..000000000 --- a/app/src/main/java/com/nextcloud/talk/models/json/push/DecryptedPushMessage.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic - * - * 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 . - */ - -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; -} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/push/DecryptedPushMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/push/DecryptedPushMessage.kt new file mode 100644 index 000000000..d541f3200 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/push/DecryptedPushMessage.kt @@ -0,0 +1,62 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017 Mario Danic + * + * 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 . + */ +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 +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt index 9c9a1a09f..a797a564d 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -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, diff --git a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt index ea12e3440..6d9074d57 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt @@ -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" }