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 81792563d..3b212a1b2 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.java @@ -34,17 +34,30 @@ import android.os.Build; import android.os.Bundle; import android.os.VibrationEffect; import android.os.Vibrator; +import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Base64; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; +import androidx.core.app.Person; +import androidx.core.graphics.drawable.IconCompat; import androidx.work.Data; import androidx.work.Worker; import androidx.work.WorkerParameters; import autodagger.AutoInjector; 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.core.ImagePipeline; +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.MagicCallActivity; import com.nextcloud.talk.activities.MainActivity; @@ -57,12 +70,10 @@ import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.json.chat.ChatUtils; 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.models.json.rooms.Conversation; import com.nextcloud.talk.models.json.rooms.RoomOverall; -import com.nextcloud.talk.utils.ApiUtils; -import com.nextcloud.talk.utils.DoNotDisturbUtils; -import com.nextcloud.talk.utils.NotificationUtils; -import com.nextcloud.talk.utils.PushUtils; +import com.nextcloud.talk.utils.*; import com.nextcloud.talk.utils.bundle.BundleKeys; import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils; import com.nextcloud.talk.utils.preferences.AppPreferences; @@ -198,12 +209,16 @@ public class NotificationWorker extends Worker { HashMap callHashMap = subjectRichParameters.get("call"); HashMap userHashMap = subjectRichParameters.get("user"); - if (callHashMap != null && callHashMap.size() > 0 && callHashMap.containsKey("call-type")) { - conversationType = callHashMap.get("call-type"); + if (callHashMap != null && callHashMap.size() > 0 && callHashMap.containsKey("name")) { + decryptedPushMessage.setSubject(callHashMap.get("name")); + } - if ("one2one".equals(conversationType)) { - decryptedPushMessage.setSubject(userHashMap.get("name")); - } + if (userHashMap != null && !userHashMap.isEmpty()) { + NotificationUser notificationUser = new NotificationUser(); + notificationUser.setId(userHashMap.get("id")); + notificationUser.setType(userHashMap.get("type")); + notificationUser.setName(userHashMap.get("name")); + decryptedPushMessage.setNotificationUser(notificationUser); } } @@ -226,7 +241,6 @@ public class NotificationWorker extends Worker { Bitmap largeIcon = null; String category; int priority = Notification.PRIORITY_HIGH; - Uri soundUri; smallIcon = R.drawable.ic_logo; @@ -263,7 +277,6 @@ public class NotificationWorker extends Worker { PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); Uri uri = Uri.parse(signatureVerification.getUserEntity().getBaseUrl()); String baseUrl = uri.getHost(); @@ -327,7 +340,6 @@ public class NotificationWorker extends Worker { notificationBuilder.setContentIntent(pendingIntent); - String stringForCrc = String.valueOf(System.currentTimeMillis()); CRC32 crc32 = new CRC32(); @@ -335,10 +347,101 @@ public class NotificationWorker extends Worker { crc32.update(groupName.getBytes()); notificationBuilder.setGroup(Long.toString(crc32.getValue())); + + // notificationId crc32 = new CRC32(); + String stringForCrc = String.valueOf(System.currentTimeMillis()); crc32.update(stringForCrc.getBytes()); + StatusBarNotification activeStatusBarNotification = + NotificationUtils.findNotificationForRoom(context, + signatureVerification.getUserEntity(), decryptedPushMessage.getId()); + + int notificationId; + + if (activeStatusBarNotification != null) { + notificationId = activeStatusBarNotification.getId(); + } else { + notificationId = (int) crc32.getValue(); + } + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + 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(decryptedPushMessage.getNotificationUser().getName()).setBot(decryptedPushMessage.getNotificationUser().getType().equals("bot")); + + notificationBuilder.setOnlyAlertOnce(true); + + if (decryptedPushMessage.getNotificationUser().getType().equals("user")) { + ImageRequest imageRequest = + DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(signatureVerification.getUserEntity().getBaseUrl(), decryptedPushMessage.getNotificationUser().getId(), R.dimen.avatar_size), 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()); + } + } else { + sendNotificationWithId(notificationId, notificationBuilder.build()); + } + + } + + 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); + + 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(), System.currentTimeMillis(), person); + return newStyle; + } + + // we'll never come here + return style; + } + + private void sendNotificationWithId(int notificationId, Notification notification) { + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + notificationManager.notify(notificationId, notification); + String ringtonePreferencesString; + Uri soundUri; + ringtonePreferencesString = appPreferences.getMessageRingtoneUri(); if (TextUtils.isEmpty(ringtonePreferencesString)) { soundUri = Uri.parse("android.resource://" + context.getPackageName() + @@ -354,9 +457,6 @@ public class NotificationWorker extends Worker { } } - - notificationManager.notify((int) crc32.getValue(), notificationBuilder.build()); - if (soundUri != null & !ApplicationWideCurrentRoomHolder.getInstance().isInCall() && DoNotDisturbUtils.shouldPlaySound()) { AudioAttributes.Builder audioAttributesBuilder = new AudioAttributes.Builder().setContentType @@ -380,7 +480,6 @@ public class NotificationWorker extends Worker { } catch (IOException e) { Log.e(TAG, "Failed to set data source"); } - } 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 index de35944c0..0f2c94bb5 100644 --- 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 @@ -51,6 +51,9 @@ public class DecryptedPushMessage { @JsonField(name = "delete-all") boolean deleteAll; + @JsonIgnore + NotificationUser notificationUser; + @JsonIgnore String text; } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/push/NotificationUser.java b/app/src/main/java/com/nextcloud/talk/models/json/push/NotificationUser.java new file mode 100644 index 000000000..ab09d45ca --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/push/NotificationUser.java @@ -0,0 +1,40 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2019 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.JsonObject; +import lombok.Data; +import org.parceler.Parcel; + +@Data +@Parcel +@JsonObject +public class NotificationUser { + @JsonField(name = "type") + String type; + + @JsonField(name = "id") + String id; + + @JsonField(name = "name") + String name; +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.java b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.java index 192af3078..d6baa6ca2 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/NotificationUtils.java @@ -130,6 +130,34 @@ public class NotificationUtils { } } + public static StatusBarNotification findNotificationForRoom(Context context, + UserEntity conversationUser, + String roomTokenOrId) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M && conversationUser.getId() != -1 && + context != null) { + + NotificationManager notificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + if (notificationManager != null) { + StatusBarNotification[] statusBarNotifications = notificationManager.getActiveNotifications(); + Notification notification; + for (StatusBarNotification statusBarNotification : statusBarNotifications) { + notification = statusBarNotification.getNotification(); + + if (notification != null && !notification.extras.isEmpty()) { + if (conversationUser.getId() == notification.extras.getLong(BundleKeys.KEY_INTERNAL_USER_ID) && + roomTokenOrId.equals(statusBarNotification.getNotification().extras.getString(BundleKeys.KEY_ROOM_TOKEN))) { + return statusBarNotification; + } + } + } + } + } + + return null; + } + public static void cancelExistingNotificationsForRoom(Context context, UserEntity conversationUser, String roomTokenOrId) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M && conversationUser.getId() != -1 &&