From 8b07a2aa72baf01162eff512753fccfb1ffd4130 Mon Sep 17 00:00:00 2001 From: Dariusz Olszewski Date: Mon, 18 Apr 2022 21:21:44 +0200 Subject: [PATCH 1/9] Reply from notification - initial implementation Signed-off-by: Dariusz Olszewski --- app/src/main/AndroidManifest.xml | 2 + .../talk/jobs/NotificationWorker.java | 35 ++++ .../talk/receivers/DirectReplyReceiver.kt | 186 ++++++++++++++++++ .../nextcloud/talk/utils/NotificationUtils.kt | 3 + 4 files changed, 226 insertions(+) create mode 100644 app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e52ddd26..e6e3536c0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -177,6 +177,8 @@ + + diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java index 86c67b5ee..a61500283 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java @@ -60,6 +60,7 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall; import com.nextcloud.talk.models.json.notifications.NotificationOverall; import com.nextcloud.talk.models.json.push.DecryptedPushMessage; import com.nextcloud.talk.models.json.push.NotificationUser; +import com.nextcloud.talk.receivers.DirectReplyReceiver; import com.nextcloud.talk.utils.ApiUtils; import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.DoNotDisturbUtils; @@ -90,6 +91,7 @@ import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.app.Person; +import androidx.core.app.RemoteInput; import androidx.core.graphics.drawable.IconCompat; import androidx.emoji.text.EmojiCompat; import androidx.work.Data; @@ -386,6 +388,7 @@ public class NotificationWorker extends Worker { "@" + decryptedPushMessage.getNotificationUser().getId()).setName(EmojiCompat.get().process(decryptedPushMessage.getNotificationUser().getName())).setBot(decryptedPushMessage.getNotificationUser().getType().equals("bot")); notificationBuilder.setOnlyAlertOnce(true); + addReplyAction(notificationBuilder, notificationId); if (decryptedPushMessage.getNotificationUser().getType().equals("user") || decryptedPushMessage.getNotificationUser().getType().equals("guest")) { String avatarUrl = ApiUtils.getUrlForAvatar(signatureVerification.getUserEntity().getBaseUrl(), @@ -434,6 +437,38 @@ public class NotificationWorker extends Worker { } + private void addReplyAction(NotificationCompat.Builder notificationBuilder, int notificationId) { + String replyLabel = context.getResources().getString(R.string.nc_reply); + + RemoteInput remoteInput = new RemoteInput.Builder(NotificationUtils.KEY_DIRECT_REPLY) + .setLabel(replyLabel) + .build(); + + // Build a PendingIntent for the reply action + Intent actualIntent = new Intent(context, DirectReplyReceiver.class); + + // NOTE - This notificationId is an internal ID used on the device only. + // It is NOT the same as the notification ID used in communication with the server. + actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_NOTIFICATION_ID(), notificationId); + actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), decryptedPushMessage.getId()); + PendingIntent replyPendingIntent = + PendingIntent.getBroadcast(getApplicationContext(), + notificationId, actualIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + NotificationCompat.Action replyAction = + new NotificationCompat.Action.Builder(R.drawable.ic_reply, replyLabel, replyPendingIntent) + .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY) + .setShowsUserInterface(false) + // Allows system to generate replies by context of conversation. + // https://developer.android.com/reference/androidx/core/app/NotificationCompat.Action.Builder#setAllowGeneratedReplies(boolean) + // Good question is - do we really want it? + .setAllowGeneratedReplies(true) + .addRemoteInput(remoteInput) + .build(); + + notificationBuilder.addAction(replyAction); + } + private NotificationCompat.MessagingStyle getStyle(Person person, @Nullable NotificationCompat.MessagingStyle style) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { NotificationCompat.MessagingStyle newStyle = diff --git a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt new file mode 100644 index 000000000..dd9eed6f4 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt @@ -0,0 +1,186 @@ +/* + * Nextcloud Talk application + * + * @author Dariusz Olszewski + * Copyright (C) 2022 Dariusz Olszewski + * + * 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.receivers + +import android.app.Notification +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.app.Person +import androidx.core.app.RemoteInput +import androidx.core.graphics.drawable.IconCompat +import autodagger.AutoInjector +import com.facebook.common.executors.UiThreadImmediateExecutorService +import com.facebook.common.references.CloseableReference +import com.facebook.datasource.DataSource +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber +import com.facebook.imagepipeline.image.CloseableImage +import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor +import com.nextcloud.talk.api.NcApi +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.models.database.UserEntity +import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.DisplayUtils +import com.nextcloud.talk.utils.NotificationUtils +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_ID +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN +import com.nextcloud.talk.utils.database.user.UserUtils +import io.reactivex.Observer +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class DirectReplyReceiver : BroadcastReceiver() { + + @Inject + @JvmField + var userUtils: UserUtils? = null + + @Inject + @JvmField + var ncApi: NcApi? = null + + lateinit var context: Context + lateinit var currentUser: UserEntity + private var notificationId: Int? = null + private var roomToken: String? = null + private var replyMessage: CharSequence? = null + + init { + NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) + } + + override fun onReceive(receiveContext: Context, intent: Intent?) { + context = receiveContext + currentUser = userUtils!!.currentUser!! + + // NOTE - This notificationId is an internal ID used on the device only. + // It is NOT the same as the notification ID used in communication with the server. + notificationId = intent!!.getIntExtra(KEY_NOTIFICATION_ID, 0) + roomToken = intent.getStringExtra(KEY_ROOM_TOKEN) + + replyMessage = getMessageText(intent) + sendDirectReply() + } + + private fun getMessageText(intent: Intent): CharSequence? { + return RemoteInput.getResultsFromIntent(intent)?.getCharSequence(NotificationUtils.KEY_DIRECT_REPLY) + } + + private fun sendDirectReply() { + val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token) + val apiVersion = ApiUtils.getChatApiVersion(currentUser, intArrayOf(1)) + val url = ApiUtils.getUrlForChat(apiVersion, currentUser.baseUrl, roomToken) + + ncApi!!.sendChatMessage(credentials, url, replyMessage, currentUser.displayName, null) + ?.subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + @RequiresApi(Build.VERSION_CODES.N) + override fun onNext(genericOverall: GenericOverall) { + loadAvatar(::confirmReplySent) + } + + override fun onError(e: Throwable) { + // TODO - inform the user that sending of the reply failed + // unused atm + } + + override fun onComplete() { + // unused atm + } + }) + } + + private fun loadAvatar(callback: (avatarIcon: IconCompat) -> Unit) { + val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false) + val imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl, currentUser) + val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null) + dataSource.subscribe(object : BaseBitmapDataSubscriber() { + override fun onNewResultImpl(bitmap: Bitmap?) { + if (bitmap != null) { + RoundAsCirclePostprocessor(true).process(bitmap) + callback(IconCompat.createWithBitmap(bitmap)) + } + } + + override fun onFailureImpl(dataSource: DataSource>) { + // unused atm + } + }, UiThreadImmediateExecutorService.getInstance()) + } + + @RequiresApi(Build.VERSION_CODES.N) + private fun findActiveNotification(notificationId: Int): Notification? { + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + return notificationManager.activeNotifications.find { it.id == notificationId }?.notification + } + + @RequiresApi(Build.VERSION_CODES.N) + private fun confirmReplySent(avatarIcon: IconCompat) { + // Implementation inspired by the SO question and article below: + // https://stackoverflow.com/questions/51549456/android-o-notification-for-direct-reply-message + // https://medium.com/@sidorovroman3/android-how-to-use-messagingstyle-for-notifications-without-caching-messages-c414ef2b816c + // + // Tries to follow "Best practices for messaging apps" described here: + // https://developer.android.com/training/notify-user/build-notification#messaging-best-practices + + // Find the original (active) notification + val previousNotification = findActiveNotification(notificationId!!) ?: return + + // Recreate builder based on the active notification + val previousBuilder = NotificationCompat.Builder(context, previousNotification) + + // Extract MessagingStyle from the active notification + val previousStyle = NotificationCompat.MessagingStyle + .extractMessagingStyleFromNotification(previousNotification) + + // Add reply + val me = Person.Builder() + .setName(currentUser.displayName) + // .setIcon(IconCompat.createWithResource(context, R.drawable.ic_user)) + .setIcon(avatarIcon) + .build() + val message = NotificationCompat.MessagingStyle.Message(replyMessage, System.currentTimeMillis(), me) + previousStyle?.addMessage(message) + + // Set the updated style + previousBuilder.setStyle(previousStyle) + + // Update the active notification. + NotificationManagerCompat.from(context).notify(notificationId!!, previousBuilder.build()) + } + +} 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 61941f251..4400d8ad9 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -61,6 +61,9 @@ object NotificationUtils { const val DEFAULT_MESSAGE_RINGTONE_URI = "android.resource://" + BuildConfig.APPLICATION_ID + "/raw/librem_by_feandesign_message" + // RemoteInput key - used for replies sent directly from notification + const val KEY_DIRECT_REPLY = "key_direct_reply" + @TargetApi(Build.VERSION_CODES.O) private fun createNotificationChannel( context: Context, From a234c178a82ddedc3f20f14c7b66d8c0c1c6dba5 Mon Sep 17 00:00:00 2001 From: Dariusz Olszewski Date: Mon, 18 Apr 2022 22:49:01 +0200 Subject: [PATCH 2/9] ktlint Signed-off-by: Dariusz Olszewski --- .../talk/receivers/DirectReplyReceiver.kt | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt index dd9eed6f4..88b85fb22 100644 --- a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt +++ b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt @@ -128,18 +128,21 @@ class DirectReplyReceiver : BroadcastReceiver() { val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false) val imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl, currentUser) val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null) - dataSource.subscribe(object : BaseBitmapDataSubscriber() { - override fun onNewResultImpl(bitmap: Bitmap?) { - if (bitmap != null) { - RoundAsCirclePostprocessor(true).process(bitmap) - callback(IconCompat.createWithBitmap(bitmap)) + dataSource.subscribe( + object : BaseBitmapDataSubscriber() { + override fun onNewResultImpl(bitmap: Bitmap?) { + if (bitmap != null) { + RoundAsCirclePostprocessor(true).process(bitmap) + callback(IconCompat.createWithBitmap(bitmap)) + } } - } - override fun onFailureImpl(dataSource: DataSource>) { - // unused atm - } - }, UiThreadImmediateExecutorService.getInstance()) + override fun onFailureImpl(dataSource: DataSource>) { + // unused atm + } + }, + UiThreadImmediateExecutorService.getInstance() + ) } @RequiresApi(Build.VERSION_CODES.N) @@ -182,5 +185,4 @@ class DirectReplyReceiver : BroadcastReceiver() { // Update the active notification. NotificationManagerCompat.from(context).notify(notificationId!!, previousBuilder.build()) } - } From b96bba47b57a49838b4c89555437c4b7a0f96b57 Mon Sep 17 00:00:00 2001 From: Dariusz Olszewski Date: Tue, 26 Apr 2022 23:00:24 +0200 Subject: [PATCH 3/9] Reduce number of issues reported by detekt Signed-off-by: Dariusz Olszewski --- .../nextcloud/talk/utils/NotificationUtils.kt | 116 +++++++++--------- 1 file changed, 56 insertions(+), 60 deletions(-) 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 4400d8ad9..83120ae78 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -182,43 +182,43 @@ object NotificationUtils { } fun cancelAllNotificationsForAccount(context: Context?, conversationUser: UserEntity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && conversationUser.id != -1L && context != null) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || conversationUser.id == -1L || context == null) { + return + } - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val statusBarNotifications = notificationManager.activeNotifications - var notification: Notification? - for (statusBarNotification in statusBarNotifications) { - notification = statusBarNotification.notification + val statusBarNotifications = notificationManager.activeNotifications + var notification: Notification? + for (statusBarNotification in statusBarNotifications) { + notification = statusBarNotification.notification - if (notification != null && !notification.extras.isEmpty) { - if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID)) { - notificationManager.cancel(statusBarNotification.id) - } + if (notification != null && !notification.extras.isEmpty) { + if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID)) { + notificationManager.cancel(statusBarNotification.id) } } } } fun cancelExistingNotificationWithId(context: Context?, conversationUser: UserEntity, notificationId: Long?) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && conversationUser.id != -1L && - context != null - ) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || conversationUser.id == -1L || context == null) { + return + } - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val statusBarNotifications = notificationManager.activeNotifications - var notification: Notification? - for (statusBarNotification in statusBarNotifications) { - notification = statusBarNotification.notification + val statusBarNotifications = notificationManager.activeNotifications + var notification: Notification? + for (statusBarNotification in statusBarNotifications) { + notification = statusBarNotification.notification - if (notification != null && !notification.extras.isEmpty) { - if ( - conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && - notificationId == notification.extras.getLong(BundleKeys.KEY_NOTIFICATION_ID) - ) { - notificationManager.cancel(statusBarNotification.id) - } + if (notification != null && !notification.extras.isEmpty) { + if ( + conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && + notificationId == notification.extras.getLong(BundleKeys.KEY_NOTIFICATION_ID) + ) { + notificationManager.cancel(statusBarNotification.id) } } } @@ -229,24 +229,23 @@ object NotificationUtils { conversationUser: UserEntity, roomTokenOrId: String ): StatusBarNotification? { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && conversationUser.id != -1L && - context != null - ) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || conversationUser.id == -1L || context == null) { + return null + } - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val statusBarNotifications = notificationManager.activeNotifications - var notification: Notification? - for (statusBarNotification in statusBarNotifications) { - notification = statusBarNotification.notification + val statusBarNotifications = notificationManager.activeNotifications + var notification: Notification? + for (statusBarNotification in statusBarNotifications) { + notification = statusBarNotification.notification - if (notification != null && !notification.extras.isEmpty) { - if ( - conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && - roomTokenOrId == statusBarNotification.notification.extras.getString(BundleKeys.KEY_ROOM_TOKEN) - ) { - return statusBarNotification - } + if (notification != null && !notification.extras.isEmpty) { + if ( + conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && + roomTokenOrId == statusBarNotification.notification.extras.getString(BundleKeys.KEY_ROOM_TOKEN) + ) { + return statusBarNotification } } } @@ -259,25 +258,22 @@ object NotificationUtils { conversationUser: UserEntity, roomTokenOrId: String ) { - if ( - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && - conversationUser.id != -1L && - context != null - ) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || conversationUser.id == -1L || context == null) { + return + } - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val statusBarNotifications = notificationManager.activeNotifications - var notification: Notification? - for (statusBarNotification in statusBarNotifications) { - notification = statusBarNotification.notification + val statusBarNotifications = notificationManager.activeNotifications + var notification: Notification? + for (statusBarNotification in statusBarNotifications) { + notification = statusBarNotification.notification - if (notification != null && !notification.extras.isEmpty) { - if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && - roomTokenOrId == statusBarNotification.notification.extras.getString(BundleKeys.KEY_ROOM_TOKEN) - ) { - notificationManager.cancel(statusBarNotification.id) - } + if (notification != null && !notification.extras.isEmpty) { + if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && + roomTokenOrId == statusBarNotification.notification.extras.getString(BundleKeys.KEY_ROOM_TOKEN) + ) { + notificationManager.cancel(statusBarNotification.id) } } } @@ -297,15 +293,15 @@ object NotificationUtils { // Notification channel will not be available when starting the application for the first time. // Ringtone uris are required to register the notification channels -> get uri from preferences. } - if (TextUtils.isEmpty(ringtonePreferencesString)) { - return Uri.parse(defaultRingtoneUri) + return if (TextUtils.isEmpty(ringtonePreferencesString)) { + Uri.parse(defaultRingtoneUri) } else { try { val ringtoneSettings = LoganSquare.parse(ringtonePreferencesString, RingtoneSettings::class.java) - return ringtoneSettings.ringtoneUri + ringtoneSettings.ringtoneUri } catch (exception: IOException) { - return Uri.parse(defaultRingtoneUri) + Uri.parse(defaultRingtoneUri) } } } From 2f73433170defd6629c53fd857ba182b01487da8 Mon Sep 17 00:00:00 2001 From: Dariusz Olszewski Date: Thu, 5 May 2022 22:21:39 +0200 Subject: [PATCH 4/9] Reduce code duplication in NotificationUtils Signed-off-by: Dariusz Olszewski --- .../nextcloud/talk/utils/NotificationUtils.kt | 131 +++++++----------- 1 file changed, 51 insertions(+), 80 deletions(-) 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 83120ae78..d4a942f89 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -181,82 +181,14 @@ object NotificationUtils { return null } - fun cancelAllNotificationsForAccount(context: Context?, conversationUser: UserEntity) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || conversationUser.id == -1L || context == null) { - return - } - - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - val statusBarNotifications = notificationManager.activeNotifications - var notification: Notification? - for (statusBarNotification in statusBarNotifications) { - notification = statusBarNotification.notification - - if (notification != null && !notification.extras.isEmpty) { - if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID)) { - notificationManager.cancel(statusBarNotification.id) - } - } - } - } - - fun cancelExistingNotificationWithId(context: Context?, conversationUser: UserEntity, notificationId: Long?) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || conversationUser.id == -1L || context == null) { - return - } - - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - val statusBarNotifications = notificationManager.activeNotifications - var notification: Notification? - for (statusBarNotification in statusBarNotifications) { - notification = statusBarNotification.notification - - if (notification != null && !notification.extras.isEmpty) { - if ( - conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && - notificationId == notification.extras.getLong(BundleKeys.KEY_NOTIFICATION_ID) - ) { - notificationManager.cancel(statusBarNotification.id) - } - } - } - } - - fun findNotificationForRoom( + private inline fun scanNotifications( context: Context?, conversationUser: UserEntity, - roomTokenOrId: String - ): StatusBarNotification? { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || conversationUser.id == -1L || context == null) { - return null - } - - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - val statusBarNotifications = notificationManager.activeNotifications - var notification: Notification? - for (statusBarNotification in statusBarNotifications) { - notification = statusBarNotification.notification - - if (notification != null && !notification.extras.isEmpty) { - if ( - conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && - roomTokenOrId == statusBarNotification.notification.extras.getString(BundleKeys.KEY_ROOM_TOKEN) - ) { - return statusBarNotification - } - } - } - - return null - } - - fun cancelExistingNotificationsForRoom( - context: Context?, - conversationUser: UserEntity, - roomTokenOrId: String + callback: ( + notificationManager: NotificationManager, + statusBarNotification: StatusBarNotification, + notification: Notification + ) -> Unit ) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || conversationUser.id == -1L || context == null) { return @@ -269,12 +201,51 @@ object NotificationUtils { for (statusBarNotification in statusBarNotifications) { notification = statusBarNotification.notification - if (notification != null && !notification.extras.isEmpty) { - if (conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && - roomTokenOrId == statusBarNotification.notification.extras.getString(BundleKeys.KEY_ROOM_TOKEN) - ) { - notificationManager.cancel(statusBarNotification.id) - } + if ( + notification != null && + !notification.extras.isEmpty && + conversationUser.id == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) + ) { + callback(notificationManager, statusBarNotification, notification) + } + } + } + + fun cancelAllNotificationsForAccount(context: Context?, conversationUser: UserEntity) { + scanNotifications(context, conversationUser) { notificationManager, statusBarNotification, _ -> + notificationManager.cancel(statusBarNotification.id) + } + } + + fun cancelExistingNotificationWithId(context: Context?, conversationUser: UserEntity, notificationId: Long?) { + scanNotifications(context, conversationUser) { notificationManager, statusBarNotification, notification -> + if (notificationId == notification.extras.getLong(BundleKeys.KEY_NOTIFICATION_ID)) { + notificationManager.cancel(statusBarNotification.id) + } + } + } + + fun findNotificationForRoom( + context: Context?, + conversationUser: UserEntity, + roomTokenOrId: String + ): StatusBarNotification? { + scanNotifications(context, conversationUser) { _, statusBarNotification, notification -> + if (roomTokenOrId == notification.extras.getString(BundleKeys.KEY_ROOM_TOKEN)) { + return statusBarNotification + } + } + return null + } + + fun cancelExistingNotificationsForRoom( + context: Context?, + conversationUser: UserEntity, + roomTokenOrId: String + ) { + scanNotifications(context, conversationUser) { notificationManager, statusBarNotification, notification -> + if (roomTokenOrId == notification.extras.getString(BundleKeys.KEY_ROOM_TOKEN)) { + notificationManager.cancel(statusBarNotification.id) } } } From 3b7ca4a31a5b3d3302135e9fbfff746381f71431 Mon Sep 17 00:00:00 2001 From: Dariusz Olszewski Date: Sat, 7 May 2022 00:07:08 +0200 Subject: [PATCH 5/9] Refactor NotificationWorker - extracted sendChatNotification method plus some minor modifications, hopefully ;-) without changing behaviour Signed-off-by: Dariusz Olszewski --- .../talk/jobs/NotificationWorker.java | 138 +++++++++--------- 1 file changed, 68 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java index a61500283..c9112583b 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java @@ -41,7 +41,6 @@ import com.facebook.common.executors.UiThreadImmediateExecutorService; import com.facebook.common.references.CloseableReference; import com.facebook.datasource.DataSource; import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.imagepipeline.core.ImagePipeline; import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor; @@ -88,7 +87,9 @@ import javax.inject.Inject; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationCompat.MessagingStyle; import androidx.core.app.NotificationManagerCompat; import androidx.core.app.Person; import androidx.core.app.RemoteInput; @@ -376,67 +377,69 @@ public class NotificationWorker extends Worker { notificationId = (int) crc32.getValue(); } - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N && decryptedPushMessage.getNotificationUser() != null && decryptedPushMessage.getType().equals("chat")) { - NotificationCompat.MessagingStyle style = null; - if (activeStatusBarNotification != null) { - Notification activeNotification = activeStatusBarNotification.getNotification(); - style = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(activeNotification); - } - - Person.Builder person = - new Person.Builder().setKey(signatureVerification.getUserEntity().getId() + - "@" + decryptedPushMessage.getNotificationUser().getId()).setName(EmojiCompat.get().process(decryptedPushMessage.getNotificationUser().getName())).setBot(decryptedPushMessage.getNotificationUser().getType().equals("bot")); - - notificationBuilder.setOnlyAlertOnce(true); - addReplyAction(notificationBuilder, notificationId); - - if (decryptedPushMessage.getNotificationUser().getType().equals("user") || decryptedPushMessage.getNotificationUser().getType().equals("guest")) { - String avatarUrl = ApiUtils.getUrlForAvatar(signatureVerification.getUserEntity().getBaseUrl(), - decryptedPushMessage.getNotificationUser().getId(), false); - - if (decryptedPushMessage.getNotificationUser().getType().equals("guest")) { - avatarUrl = ApiUtils.getUrlForGuestAvatar(signatureVerification.getUserEntity().getBaseUrl(), - decryptedPushMessage.getNotificationUser().getName(), - false); - } - - ImageRequest imageRequest = - DisplayUtils.getImageRequestForUrl(avatarUrl, null); - ImagePipeline imagePipeline = Fresco.getImagePipeline(); - DataSource> dataSource = imagePipeline.fetchDecodedImage(imageRequest, context); - - NotificationCompat.MessagingStyle finalStyle = style; - dataSource.subscribe( - new BaseBitmapDataSubscriber() { - @Override - protected void onNewResultImpl(Bitmap bitmap) { - if (bitmap != null) { - new RoundAsCirclePostprocessor(true).process(bitmap); - person.setIcon(IconCompat.createWithBitmap(bitmap)); - notificationBuilder.setStyle(getStyle(person.build(), - finalStyle)); - sendNotificationWithId(notificationId, notificationBuilder.build()); - - } - } - - @Override - protected void onFailureImpl(DataSource> dataSource) { - notificationBuilder.setStyle(getStyle(person.build(), finalStyle)); - sendNotificationWithId(notificationId, notificationBuilder.build()); - } - }, - UiThreadImmediateExecutorService.getInstance()); - } else { - notificationBuilder.setStyle(getStyle(person.build(), style)); - sendNotificationWithId(notificationId, notificationBuilder.build()); - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && + CHAT.equals(decryptedPushMessage.getType()) && + decryptedPushMessage.getNotificationUser() != null) { + sendChatNotification(notificationBuilder, activeStatusBarNotification, notificationId); } else { sendNotificationWithId(notificationId, notificationBuilder.build()); } } + @RequiresApi(api = Build.VERSION_CODES.N) + private void sendChatNotification(NotificationCompat.Builder notificationBuilder, + StatusBarNotification activeStatusBarNotification, + int notificationId) { + + final NotificationUser notificationUser = decryptedPushMessage.getNotificationUser(); + final String userType = notificationUser.getType(); + + MessagingStyle style = activeStatusBarNotification != null ? + MessagingStyle.extractMessagingStyleFromNotification(activeStatusBarNotification.getNotification()) : + null; + + Person.Builder person = + new Person.Builder() + .setKey(signatureVerification.getUserEntity().getId() + "@" + notificationUser.getId()) + .setName(EmojiCompat.get().process(notificationUser.getName())) + .setBot("bot".equals(userType)); + + notificationBuilder.setOnlyAlertOnce(true); + addReplyAction(notificationBuilder, notificationId); + + if ("user".equals(userType) || "guest".equals(userType)) { + String baseUrl = signatureVerification.getUserEntity().getBaseUrl(); + String avatarUrl = "user".equals(userType) ? + ApiUtils.getUrlForAvatar(baseUrl, notificationUser.getId(), false) : + ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.getName(), false); + + ImageRequest imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl, null); + Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context).subscribe( + new BaseBitmapDataSubscriber() { + @Override + protected void onNewResultImpl(Bitmap bitmap) { + if (bitmap != null) { + new RoundAsCirclePostprocessor(true).process(bitmap); + person.setIcon(IconCompat.createWithBitmap(bitmap)); + notificationBuilder.setStyle(getStyle(person.build(), style)); + sendNotificationWithId(notificationId, notificationBuilder.build()); + } + } + + @Override + protected void onFailureImpl(DataSource> dataSource) { + notificationBuilder.setStyle(getStyle(person.build(), style)); + sendNotificationWithId(notificationId, notificationBuilder.build()); + } + }, + UiThreadImmediateExecutorService.getInstance()); + } else { + notificationBuilder.setStyle(getStyle(person.build(), style)); + sendNotificationWithId(notificationId, notificationBuilder.build()); + } + } + private void addReplyAction(NotificationCompat.Builder notificationBuilder, int notificationId) { String replyLabel = context.getResources().getString(R.string.nc_reply); @@ -469,24 +472,19 @@ public class NotificationWorker extends Worker { notificationBuilder.addAction(replyAction); } - private NotificationCompat.MessagingStyle getStyle(Person person, @Nullable NotificationCompat.MessagingStyle style) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - NotificationCompat.MessagingStyle newStyle = - new NotificationCompat.MessagingStyle(person); + @RequiresApi(api = Build.VERSION_CODES.N) + private MessagingStyle getStyle(Person person, @Nullable MessagingStyle style) { + MessagingStyle newStyle = new MessagingStyle(person); - newStyle.setConversationTitle(decryptedPushMessage.getSubject()); - newStyle.setGroupConversation(!conversationType.equals("one2one")); + newStyle.setConversationTitle(decryptedPushMessage.getSubject()); + newStyle.setGroupConversation(!conversationType.equals("one2one")); - if (style != null) { - style.getMessages().forEach(message -> newStyle.addMessage(new NotificationCompat.MessagingStyle.Message(message.getText(), message.getTimestamp(), message.getPerson()))); - } - - newStyle.addMessage(decryptedPushMessage.getText(), decryptedPushMessage.getTimestamp(), person); - return newStyle; + if (style != null) { + style.getMessages().forEach(message -> newStyle.addMessage(new MessagingStyle.Message(message.getText(), message.getTimestamp(), message.getPerson()))); } - // we'll never come here - return style; + newStyle.addMessage(decryptedPushMessage.getText(), decryptedPushMessage.getTimestamp(), person); + return newStyle; } private void sendNotificationWithId(int notificationId, Notification notification) { From d4bdd88588212ad194637d2b079b876c8b9df791 Mon Sep 17 00:00:00 2001 From: Dariusz Olszewski Date: Mon, 9 May 2022 21:51:07 +0200 Subject: [PATCH 6/9] Refactor - extracted common method to load avatars for notifications Signed-off-by: Dariusz Olszewski --- .../talk/jobs/NotificationWorker.java | 45 ++++--------------- .../talk/receivers/DirectReplyReceiver.kt | 39 ++-------------- .../nextcloud/talk/utils/NotificationUtils.kt | 31 +++++++++++++ 3 files changed, 43 insertions(+), 72 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java index c9112583b..aeb057dd1 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java @@ -37,14 +37,6 @@ import android.util.Base64; import android.util.Log; import com.bluelinelabs.logansquare.LoganSquare; -import com.facebook.common.executors.UiThreadImmediateExecutorService; -import com.facebook.common.references.CloseableReference; -import com.facebook.datasource.DataSource; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; -import com.facebook.imagepipeline.image.CloseableImage; -import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor; -import com.facebook.imagepipeline.request.ImageRequest; import com.nextcloud.talk.R; import com.nextcloud.talk.activities.CallActivity; import com.nextcloud.talk.activities.MainActivity; @@ -61,7 +53,6 @@ import com.nextcloud.talk.models.json.push.DecryptedPushMessage; import com.nextcloud.talk.models.json.push.NotificationUser; import com.nextcloud.talk.receivers.DirectReplyReceiver; import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.DoNotDisturbUtils; import com.nextcloud.talk.utils.NotificationUtils; import com.nextcloud.talk.utils.PushUtils; @@ -93,7 +84,6 @@ import androidx.core.app.NotificationCompat.MessagingStyle; import androidx.core.app.NotificationManagerCompat; import androidx.core.app.Person; import androidx.core.app.RemoteInput; -import androidx.core.graphics.drawable.IconCompat; import androidx.emoji.text.EmojiCompat; import androidx.work.Data; import androidx.work.Worker; @@ -395,9 +385,10 @@ public class NotificationWorker extends Worker { final NotificationUser notificationUser = decryptedPushMessage.getNotificationUser(); final String userType = notificationUser.getType(); - MessagingStyle style = activeStatusBarNotification != null ? - MessagingStyle.extractMessagingStyleFromNotification(activeStatusBarNotification.getNotification()) : - null; + MessagingStyle style = null; + if (activeStatusBarNotification != null) { + style = MessagingStyle.extractMessagingStyleFromNotification(activeStatusBarNotification.getNotification()); + } Person.Builder person = new Person.Builder() @@ -413,31 +404,11 @@ public class NotificationWorker extends Worker { String avatarUrl = "user".equals(userType) ? ApiUtils.getUrlForAvatar(baseUrl, notificationUser.getId(), false) : ApiUtils.getUrlForGuestAvatar(baseUrl, notificationUser.getName(), false); - - ImageRequest imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl, null); - Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context).subscribe( - new BaseBitmapDataSubscriber() { - @Override - protected void onNewResultImpl(Bitmap bitmap) { - if (bitmap != null) { - new RoundAsCirclePostprocessor(true).process(bitmap); - person.setIcon(IconCompat.createWithBitmap(bitmap)); - notificationBuilder.setStyle(getStyle(person.build(), style)); - sendNotificationWithId(notificationId, notificationBuilder.build()); - } - } - - @Override - protected void onFailureImpl(DataSource> dataSource) { - notificationBuilder.setStyle(getStyle(person.build(), style)); - sendNotificationWithId(notificationId, notificationBuilder.build()); - } - }, - UiThreadImmediateExecutorService.getInstance()); - } else { - notificationBuilder.setStyle(getStyle(person.build(), style)); - sendNotificationWithId(notificationId, notificationBuilder.build()); + person.setIcon(NotificationUtils.INSTANCE.loadAvatarSync(avatarUrl)); } + + notificationBuilder.setStyle(getStyle(person.build(), style)); + sendNotificationWithId(notificationId, notificationBuilder.build()); } private void addReplyAction(NotificationCompat.Builder notificationBuilder, int notificationId) { diff --git a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt index 88b85fb22..24249c5e1 100644 --- a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt +++ b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt @@ -25,28 +25,18 @@ import android.app.NotificationManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.graphics.Bitmap import android.os.Build import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.Person import androidx.core.app.RemoteInput -import androidx.core.graphics.drawable.IconCompat import autodagger.AutoInjector -import com.facebook.common.executors.UiThreadImmediateExecutorService -import com.facebook.common.references.CloseableReference -import com.facebook.datasource.DataSource -import com.facebook.drawee.backends.pipeline.Fresco -import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber -import com.facebook.imagepipeline.image.CloseableImage -import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_ID import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN @@ -110,7 +100,7 @@ class DirectReplyReceiver : BroadcastReceiver() { @RequiresApi(Build.VERSION_CODES.N) override fun onNext(genericOverall: GenericOverall) { - loadAvatar(::confirmReplySent) + confirmReplySent() } override fun onError(e: Throwable) { @@ -124,27 +114,6 @@ class DirectReplyReceiver : BroadcastReceiver() { }) } - private fun loadAvatar(callback: (avatarIcon: IconCompat) -> Unit) { - val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false) - val imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl, currentUser) - val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null) - dataSource.subscribe( - object : BaseBitmapDataSubscriber() { - override fun onNewResultImpl(bitmap: Bitmap?) { - if (bitmap != null) { - RoundAsCirclePostprocessor(true).process(bitmap) - callback(IconCompat.createWithBitmap(bitmap)) - } - } - - override fun onFailureImpl(dataSource: DataSource>) { - // unused atm - } - }, - UiThreadImmediateExecutorService.getInstance() - ) - } - @RequiresApi(Build.VERSION_CODES.N) private fun findActiveNotification(notificationId: Int): Notification? { val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager @@ -152,7 +121,7 @@ class DirectReplyReceiver : BroadcastReceiver() { } @RequiresApi(Build.VERSION_CODES.N) - private fun confirmReplySent(avatarIcon: IconCompat) { + private fun confirmReplySent() { // Implementation inspired by the SO question and article below: // https://stackoverflow.com/questions/51549456/android-o-notification-for-direct-reply-message // https://medium.com/@sidorovroman3/android-how-to-use-messagingstyle-for-notifications-without-caching-messages-c414ef2b816c @@ -171,10 +140,10 @@ class DirectReplyReceiver : BroadcastReceiver() { .extractMessagingStyleFromNotification(previousNotification) // Add reply + val avatarUrl = ApiUtils.getUrlForAvatar(currentUser.baseUrl, currentUser.userId, false) val me = Person.Builder() .setName(currentUser.displayName) - // .setIcon(IconCompat.createWithResource(context, R.drawable.ic_user)) - .setIcon(avatarIcon) + .setIcon(NotificationUtils.loadAvatarSync(avatarUrl)) .build() val message = NotificationCompat.MessagingStyle.Message(replyMessage, System.currentTimeMillis(), me) previousStyle?.addMessage(message) 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 d4a942f89..a73a76179 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.kt @@ -32,7 +32,13 @@ import android.net.Uri import android.os.Build import android.service.notification.StatusBarNotification import android.text.TextUtils +import androidx.core.graphics.drawable.IconCompat import com.bluelinelabs.logansquare.LoganSquare +import com.facebook.common.references.CloseableReference +import com.facebook.datasource.DataSources +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.imagepipeline.image.CloseableBitmap +import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.R import com.nextcloud.talk.models.RingtoneSettings @@ -297,6 +303,31 @@ object NotificationUtils { ) } + /* + * Load user avatar synchronously. + * Inspired by: + * https://frescolib.org/docs/using-image-pipeline.html + * https://github.com/facebook/fresco/issues/830 + * https://localcoder.org/using-facebooks-fresco-to-load-a-bitmap + */ + fun loadAvatarSync(avatarUrl: String): IconCompat? { + // TODO - how to handle errors here? + var avatarIcon: IconCompat? = null + val imageRequest = DisplayUtils.getImageRequestForUrl(avatarUrl, null) + val dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, null) + val closeableImageRef = DataSources.waitForFinalResult(dataSource) as CloseableReference? + val bitmap = closeableImageRef?.get()?.underlyingBitmap + if (bitmap != null) { + // According to Fresco documentation a copy of the bitmap should be made before closing the references. + // However, it seems to work without making a copy... ;-) + RoundAsCirclePostprocessor(true).process(bitmap) + avatarIcon = IconCompat.createWithBitmap(bitmap) + } + CloseableReference.closeSafely(closeableImageRef) + dataSource.close() + return avatarIcon + } + private data class Channel( val id: String, val name: String, From 93e07519011dafef9902994c1c9371aa1b68bae5 Mon Sep 17 00:00:00 2001 From: Dariusz Olszewski Date: Wed, 11 May 2022 22:01:19 +0200 Subject: [PATCH 7/9] Additional refactoring/clean-up Signed-off-by: Dariusz Olszewski --- .../talk/jobs/NotificationWorker.java | 58 +++++++++---------- .../talk/receivers/DirectReplyReceiver.kt | 12 ++-- .../nextcloud/talk/utils/bundle/BundleKeys.kt | 1 + 3 files changed, 33 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java index aeb057dd1..3f2528286 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java @@ -294,8 +294,7 @@ public class NotificationWorker extends Worker { intent.setAction(Long.toString(System.currentTimeMillis())); - PendingIntent pendingIntent = PendingIntent.getActivity(context, - 0, intent, 0); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); Uri uri = Uri.parse(signatureVerification.getUserEntity().getBaseUrl()); String baseUrl = uri.getHost(); @@ -343,44 +342,41 @@ public class NotificationWorker extends Worker { notificationBuilder.setContentIntent(pendingIntent); - - CRC32 crc32 = new CRC32(); - String groupName = signatureVerification.getUserEntity().getId() + "@" + decryptedPushMessage.getId(); - crc32.update(groupName.getBytes()); - notificationBuilder.setGroup(Long.toString(crc32.getValue())); - - // notificationId - crc32 = new CRC32(); - String stringForCrc = String.valueOf(System.currentTimeMillis()); - crc32.update(stringForCrc.getBytes()); + notificationBuilder.setGroup(Long.toString(calculateCRC32(groupName))); StatusBarNotification activeStatusBarNotification = NotificationUtils.INSTANCE.findNotificationForRoom(context, signatureVerification.getUserEntity(), decryptedPushMessage.getId()); - int notificationId; - + // NOTE - systemNotificationId is an internal ID used on the device only. + // It is NOT the same as the notification ID used in communication with the server. + int systemNotificationId; if (activeStatusBarNotification != null) { - notificationId = activeStatusBarNotification.getId(); + systemNotificationId = activeStatusBarNotification.getId(); } else { - notificationId = (int) crc32.getValue(); + systemNotificationId = (int) calculateCRC32(String.valueOf(System.currentTimeMillis())); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && CHAT.equals(decryptedPushMessage.getType()) && decryptedPushMessage.getNotificationUser() != null) { - sendChatNotification(notificationBuilder, activeStatusBarNotification, notificationId); - } else { - sendNotificationWithId(notificationId, notificationBuilder.build()); + prepareChatNotification(notificationBuilder, activeStatusBarNotification, systemNotificationId); } + sendNotification(systemNotificationId, notificationBuilder.build()); + } + + private long calculateCRC32(String s) { + CRC32 crc32 = new CRC32(); + crc32.update(s.getBytes()); + return crc32.getValue(); } @RequiresApi(api = Build.VERSION_CODES.N) - private void sendChatNotification(NotificationCompat.Builder notificationBuilder, - StatusBarNotification activeStatusBarNotification, - int notificationId) { + private void prepareChatNotification(NotificationCompat.Builder notificationBuilder, + StatusBarNotification activeStatusBarNotification, + int systemNotificationId) { final NotificationUser notificationUser = decryptedPushMessage.getNotificationUser(); final String userType = notificationUser.getType(); @@ -397,7 +393,7 @@ public class NotificationWorker extends Worker { .setBot("bot".equals(userType)); notificationBuilder.setOnlyAlertOnce(true); - addReplyAction(notificationBuilder, notificationId); + addReplyAction(notificationBuilder, systemNotificationId); if ("user".equals(userType) || "guest".equals(userType)) { String baseUrl = signatureVerification.getUserEntity().getBaseUrl(); @@ -408,10 +404,10 @@ public class NotificationWorker extends Worker { } notificationBuilder.setStyle(getStyle(person.build(), style)); - sendNotificationWithId(notificationId, notificationBuilder.build()); } - private void addReplyAction(NotificationCompat.Builder notificationBuilder, int notificationId) { + @RequiresApi(api = Build.VERSION_CODES.N) + private void addReplyAction(NotificationCompat.Builder notificationBuilder, int systemNotificationId) { String replyLabel = context.getResources().getString(R.string.nc_reply); RemoteInput remoteInput = new RemoteInput.Builder(NotificationUtils.KEY_DIRECT_REPLY) @@ -421,13 +417,12 @@ public class NotificationWorker extends Worker { // Build a PendingIntent for the reply action Intent actualIntent = new Intent(context, DirectReplyReceiver.class); - // NOTE - This notificationId is an internal ID used on the device only. + // NOTE - systemNotificationId is an internal ID used on the device only. // It is NOT the same as the notification ID used in communication with the server. - actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_NOTIFICATION_ID(), notificationId); + actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_SYSTEM_NOTIFICATION_ID(), systemNotificationId); actualIntent.putExtra(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), decryptedPushMessage.getId()); PendingIntent replyPendingIntent = - PendingIntent.getBroadcast(getApplicationContext(), - notificationId, actualIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent.getBroadcast(context, systemNotificationId, actualIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(R.drawable.ic_reply, replyLabel, replyPendingIntent) @@ -458,7 +453,7 @@ public class NotificationWorker extends Worker { return newStyle; } - private void sendNotificationWithId(int notificationId, Notification notification) { + private void sendNotification(int notificationId, Notification notification) { NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); notificationManager.notify(notificationId, notification); @@ -469,8 +464,7 @@ public class NotificationWorker extends Worker { } if (!notification.category.equals(Notification.CATEGORY_CALL) || !muteCall) { - Uri soundUri = NotificationUtils.INSTANCE.getMessageRingtoneUri(getApplicationContext(), - appPreferences); + Uri soundUri = NotificationUtils.INSTANCE.getMessageRingtoneUri(context, appPreferences); if (soundUri != null && !ApplicationWideCurrentRoomHolder.getInstance().isInCall() && (DoNotDisturbUtils.INSTANCE.shouldPlaySound() || importantConversation)) { AudioAttributes.Builder audioAttributesBuilder = new AudioAttributes.Builder().setContentType diff --git a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt index 24249c5e1..26caea024 100644 --- a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt +++ b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt @@ -38,8 +38,8 @@ import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.NotificationUtils -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_ID import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN +import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SYSTEM_NOTIFICATION_ID import com.nextcloud.talk.utils.database.user.UserUtils import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers @@ -60,7 +60,7 @@ class DirectReplyReceiver : BroadcastReceiver() { lateinit var context: Context lateinit var currentUser: UserEntity - private var notificationId: Int? = null + private var systemNotificationId: Int? = null private var roomToken: String? = null private var replyMessage: CharSequence? = null @@ -72,9 +72,9 @@ class DirectReplyReceiver : BroadcastReceiver() { context = receiveContext currentUser = userUtils!!.currentUser!! - // NOTE - This notificationId is an internal ID used on the device only. + // NOTE - systemNotificationId is an internal ID used on the device only. // It is NOT the same as the notification ID used in communication with the server. - notificationId = intent!!.getIntExtra(KEY_NOTIFICATION_ID, 0) + systemNotificationId = intent!!.getIntExtra(KEY_SYSTEM_NOTIFICATION_ID, 0) roomToken = intent.getStringExtra(KEY_ROOM_TOKEN) replyMessage = getMessageText(intent) @@ -130,7 +130,7 @@ class DirectReplyReceiver : BroadcastReceiver() { // https://developer.android.com/training/notify-user/build-notification#messaging-best-practices // Find the original (active) notification - val previousNotification = findActiveNotification(notificationId!!) ?: return + val previousNotification = findActiveNotification(systemNotificationId!!) ?: return // Recreate builder based on the active notification val previousBuilder = NotificationCompat.Builder(context, previousNotification) @@ -152,6 +152,6 @@ class DirectReplyReceiver : BroadcastReceiver() { previousBuilder.setStyle(previousStyle) // Update the active notification. - NotificationManagerCompat.from(context).notify(notificationId!!, previousBuilder.build()) + NotificationManagerCompat.from(context).notify(systemNotificationId!!, previousBuilder.build()) } } 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 ff862156b..58d60e445 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 @@ -71,4 +71,5 @@ object BundleKeys { val KEY_FORWARD_MSG_FLAG = "KEY_FORWARD_MSG_FLAG" val KEY_FORWARD_MSG_TEXT = "KEY_FORWARD_MSG_TEXT" val KEY_FORWARD_HIDE_SOURCE_ROOM = "KEY_FORWARD_HIDE_SOURCE_ROOM" + val KEY_SYSTEM_NOTIFICATION_ID = "KEY_SYSTEM_NOTIFICATION_ID" } From 4bb4d6870a5f1b79f232f054d138fc3fdcc4a674 Mon Sep 17 00:00:00 2001 From: Dariusz Olszewski Date: Wed, 11 May 2022 22:15:17 +0200 Subject: [PATCH 8/9] This setAction call seems strange and redundant Signed-off-by: Dariusz Olszewski --- .../main/java/com/nextcloud/talk/jobs/NotificationWorker.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java index 3f2528286..da1b8d074 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java @@ -292,8 +292,6 @@ public class NotificationWorker extends Worker { } } - intent.setAction(Long.toString(System.currentTimeMillis())); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); Uri uri = Uri.parse(signatureVerification.getUserEntity().getBaseUrl()); From bef443e2729fa8bc687355d3f2021136a34668c9 Mon Sep 17 00:00:00 2001 From: drone Date: Fri, 13 May 2022 08:49:04 +0000 Subject: [PATCH 9/9] Drone: update FindBugs results to reflect reduced error/warning count [skip ci] Signed-off-by: drone --- scripts/analysis/findbugs-results.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/analysis/findbugs-results.txt b/scripts/analysis/findbugs-results.txt index 7b89b2202..6b3ed8d68 100644 --- a/scripts/analysis/findbugs-results.txt +++ b/scripts/analysis/findbugs-results.txt @@ -1 +1 @@ -406 \ No newline at end of file +400 \ No newline at end of file