diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt index c5bccc97f5..a6c3f93ecf 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.androidutils.system import android.annotation.SuppressLint import android.annotation.TargetApi import android.app.Activity +import android.app.NotificationManager import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent @@ -72,6 +73,17 @@ fun Context.getApplicationLabel(packageName: String): String { } } +/** + * Return true it the user has enabled the do not disturb mode. + */ +fun isDoNotDisturbModeOn(context: Context): Boolean { + // We cannot use NotificationManagerCompat here. + val setting = context.getSystemService()!!.currentInterruptionFilter + + return setting == NotificationManager.INTERRUPTION_FILTER_NONE || + setting == NotificationManager.INTERRUPTION_FILTER_ALARMS +} + /** * display the system dialog for granting this permission. If previously granted, the * system will not show it (so you should call this method). diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt index 1a2d4a852f..4bb49e168f 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactory.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.push.impl.notifications.factories.NotificationFactory import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent @@ -26,8 +27,9 @@ import javax.inject.Inject private typealias ProcessedMessageEvents = List> +// TODO Find a better name, it clashes with io.element.android.libraries.push.impl.notifications.factories.NotificationFactory class NotificationFactory @Inject constructor( - private val notificationUtils: NotificationUtils, + private val notificationFactory: NotificationFactory, private val roomGroupMessageCreator: RoomGroupMessageCreator, private val summaryGroupMessageCreator: SummaryGroupMessageCreator ) { @@ -66,7 +68,7 @@ class NotificationFactory @Inject constructor( when (processed) { ProcessedEvent.Type.REMOVE -> OneShotNotification.Removed(key = event.roomId.value) ProcessedEvent.Type.KEEP -> OneShotNotification.Append( - notificationUtils.buildRoomInvitationNotification(event), + notificationFactory.createRoomInvitationNotification(event), OneShotNotification.Append.Meta( key = event.roomId.value, summaryLine = event.description, @@ -84,7 +86,7 @@ class NotificationFactory @Inject constructor( when (processed) { ProcessedEvent.Type.REMOVE -> OneShotNotification.Removed(key = event.eventId.value) ProcessedEvent.Type.KEEP -> OneShotNotification.Append( - notificationUtils.buildSimpleEventNotification(event), + notificationFactory.createSimpleEventNotification(event), OneShotNotification.Append.Meta( key = event.eventId.value, summaryLine = event.description, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt index 78ab059275..29cdff44db 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt @@ -20,47 +20,18 @@ package io.element.android.libraries.push.impl.notifications import android.Manifest import android.annotation.SuppressLint -import android.app.Notification -import android.app.NotificationManager import android.content.Context import android.content.pm.PackageManager -import android.graphics.Bitmap -import android.graphics.Canvas -import androidx.annotation.DrawableRes import androidx.core.app.ActivityCompat -import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import androidx.core.content.ContextCompat -import androidx.core.content.getSystemService -import androidx.core.content.res.ResourcesCompat -import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.ApplicationContext -import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.matrix.api.core.ThreadId -import io.element.android.libraries.push.impl.R -import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels -import io.element.android.libraries.push.impl.notifications.factories.PendingIntentFactory -import io.element.android.libraries.push.impl.notifications.factories.action.AcceptInvitationActionFactory -import io.element.android.libraries.push.impl.notifications.factories.action.MarkAsReadActionFactory -import io.element.android.libraries.push.impl.notifications.factories.action.QuickReplyActionFactory -import io.element.android.libraries.push.impl.notifications.factories.action.RejectInvitationActionFactory -import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent -import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent -import io.element.android.services.toolbox.api.strings.StringProvider +import io.element.android.libraries.push.impl.notifications.factories.NotificationFactory import timber.log.Timber import javax.inject.Inject class NotificationUtils @Inject constructor( @ApplicationContext private val context: Context, - private val notificationChannels: NotificationChannels, - // private val vectorPreferences: VectorPreferences, - private val stringProvider: StringProvider, - private val buildMeta: BuildMeta, - private val pendingIntentFactory: PendingIntentFactory, - private val markAsReadActionFactory: MarkAsReadActionFactory, - private val quickReplyActionFactory: QuickReplyActionFactory, - private val rejectInvitationActionFactory: RejectInvitationActionFactory, - private val acceptInvitationActionFactory: AcceptInvitationActionFactory, + private val notificationFactory: NotificationFactory, ) { companion object { @@ -79,228 +50,6 @@ class NotificationUtils @Inject constructor( private val notificationManager = NotificationManagerCompat.from(context) - /** - * Build a notification for a Room. - */ - fun buildMessagesListNotification( - messageStyle: NotificationCompat.MessagingStyle, - roomInfo: RoomEventGroupInfo, - threadId: ThreadId?, - largeIcon: Bitmap?, - lastMessageTimestamp: Long, - tickerText: String - ): Notification { - val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) - // Build the pending intent for when the notification is clicked - val openIntent = when { - threadId != null && - true - /** TODO EAx vectorPreferences.areThreadMessagesEnabled() */ - -> pendingIntentFactory.createOpenThreadPendingIntent(roomInfo, threadId) - - else -> pendingIntentFactory.createOpenRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId) - } - - val smallIcon = R.drawable.ic_notification - - val channelId = notificationChannels.getChannelIdForMessage(roomInfo.shouldBing) - return NotificationCompat.Builder(context, channelId) - .setOnlyAlertOnce(roomInfo.isUpdated) - .setWhen(lastMessageTimestamp) - // MESSAGING_STYLE sets title and content for API 16 and above devices. - .setStyle(messageStyle) - // A category allows groups of notifications to be ranked and filtered – per user or system settings. - // For example, alarm notifications should display before promo notifications, or message from known contact - // that can be displayed in not disturb mode if white listed (the later will need compat28.x) - .setCategory(NotificationCompat.CATEGORY_MESSAGE) - // ID of the corresponding shortcut, for conversation features under API 30+ - .setShortcutId(roomInfo.roomId.value) - // Title for API < 16 devices. - .setContentTitle(roomInfo.roomDisplayName) - // Content for API < 16 devices. - .setContentText(stringProvider.getString(R.string.notification_new_messages)) - // Number of new notifications for API <24 (M and below) devices. - .setSubText( - stringProvider.getQuantityString( - R.plurals.notification_new_messages_for_room, - messageStyle.messages.size, - messageStyle.messages.size - ) - ) - // Auto-bundling is enabled for 4 or more notifications on API 24+ (N+) - // devices and all Wear devices. But we want a custom grouping, so we specify the groupID - .setGroup(roomInfo.sessionId.value) - // In order to avoid notification making sound twice (due to the summary notification) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) - .setSmallIcon(smallIcon) - // Set primary color (important for Wear 2.0 Notifications). - .setColor(accentColor) - // Sets priority for 25 and below. For 26 and above, 'priority' is deprecated for - // 'importance' which is set in the NotificationChannel. The integers representing - // 'priority' are different from 'importance', so make sure you don't mix them. - .apply { - if (roomInfo.shouldBing) { - // Compat - priority = NotificationCompat.PRIORITY_DEFAULT - /* - vectorPreferences.getNotificationRingTone()?.let { - setSound(it) - } - */ - setLights(accentColor, 500, 500) - } else { - priority = NotificationCompat.PRIORITY_LOW - } - - // Add actions and notification intents - // Mark room as read - addAction(markAsReadActionFactory.create(roomInfo)) - // Quick reply - if (!roomInfo.hasSmartReplyError) { - addAction(quickReplyActionFactory.create(roomInfo, threadId)) - } - if (openIntent != null) { - setContentIntent(openIntent) - } - if (largeIcon != null) { - setLargeIcon(largeIcon) - } - setDeleteIntent(pendingIntentFactory.createDismissRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId)) - } - .setTicker(tickerText) - .build() - } - - fun buildRoomInvitationNotification( - inviteNotifiableEvent: InviteNotifiableEvent - ): Notification { - val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) - val smallIcon = R.drawable.ic_notification - val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy) - return NotificationCompat.Builder(context, channelId) - .setOnlyAlertOnce(true) - .setContentTitle(inviteNotifiableEvent.roomName ?: buildMeta.applicationName) - .setContentText(inviteNotifiableEvent.description) - .setGroup(inviteNotifiableEvent.sessionId.value) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) - .setSmallIcon(smallIcon) - .setColor(accentColor) - .addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent)) - .addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent)) - .apply { - /* - // Build the pending intent for when the notification is clicked - val contentIntent = HomeActivity.newIntent( - context, - firstStartMainActivity = true, - inviteNotificationRoomId = inviteNotifiableEvent.roomId - ) - contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP - // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that - contentIntent.data = createIgnoredUri(inviteNotifiableEvent.eventId) - setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, PendingIntentCompat.FLAG_IMMUTABLE)) - - */ - - if (inviteNotifiableEvent.noisy) { - // Compat - priority = NotificationCompat.PRIORITY_DEFAULT - /* - vectorPreferences.getNotificationRingTone()?.let { - setSound(it) - } - */ - setLights(accentColor, 500, 500) - } else { - priority = NotificationCompat.PRIORITY_LOW - } - setAutoCancel(true) - } - .build() - } - - fun buildSimpleEventNotification( - simpleNotifiableEvent: SimpleNotifiableEvent, - ): Notification { - val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) - val smallIcon = R.drawable.ic_notification - - val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy) - return NotificationCompat.Builder(context, channelId) - .setOnlyAlertOnce(true) - .setContentTitle(buildMeta.applicationName) - .setContentText(simpleNotifiableEvent.description) - .setGroup(simpleNotifiableEvent.sessionId.value) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) - .setSmallIcon(smallIcon) - .setColor(accentColor) - .setAutoCancel(true) - .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent(simpleNotifiableEvent.sessionId, simpleNotifiableEvent.roomId)) - .apply { - if (simpleNotifiableEvent.noisy) { - // Compat - priority = NotificationCompat.PRIORITY_DEFAULT - /* - vectorPreferences.getNotificationRingTone()?.let { - setSound(it) - } - */ - setLights(accentColor, 500, 500) - } else { - priority = NotificationCompat.PRIORITY_LOW - } - setAutoCancel(true) - } - .build() - } - - /** - * Build the summary notification. - */ - fun buildSummaryListNotification( - sessionId: SessionId, - style: NotificationCompat.InboxStyle?, - compatSummary: String, - noisy: Boolean, - lastMessageTimestamp: Long - ): Notification { - val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) - val smallIcon = R.drawable.ic_notification - val channelId = notificationChannels.getChannelIdForMessage(noisy) - return NotificationCompat.Builder(context, channelId) - .setOnlyAlertOnce(true) - // used in compat < N, after summary is built based on child notifications - .setWhen(lastMessageTimestamp) - .setStyle(style) - .setContentTitle(sessionId.value) - .setCategory(NotificationCompat.CATEGORY_MESSAGE) - .setSmallIcon(smallIcon) - // set content text to support devices running API level < 24 - .setContentText(compatSummary) - .setGroup(sessionId.value) - // set this notification as the summary for the group - .setGroupSummary(true) - .setColor(accentColor) - .apply { - if (noisy) { - // Compat - priority = NotificationCompat.PRIORITY_DEFAULT - /* - vectorPreferences.getNotificationRingTone()?.let { - setSound(it) - } - */ - setLights(accentColor, 500, 500) - } else { - // compat - priority = NotificationCompat.PRIORITY_LOW - } - } - .setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(sessionId)) - .setDeleteIntent(pendingIntentFactory.createDismissSummaryPendingIntent(sessionId)) - .build() - } - /** * Cancel the foreground notification service. */ @@ -329,55 +78,7 @@ class NotificationUtils @Inject constructor( notificationManager.notify( "DIAGNOSTIC", 888, - NotificationCompat.Builder(context, notificationChannels.getChannelIdForTest()) - .setContentTitle(buildMeta.applicationName) - .setContentText(stringProvider.getString(R.string.notification_test_push_notification_content)) - .setSmallIcon(R.drawable.ic_notification) - .setLargeIcon(getBitmap(context, R.drawable.element_logo_green)) - .setColor(ContextCompat.getColor(context, R.color.notification_accent_color)) - .setPriority(NotificationCompat.PRIORITY_MAX) - .setCategory(NotificationCompat.CATEGORY_STATUS) - .setAutoCancel(true) - .setContentIntent(pendingIntentFactory.createTestPendingIntent()) - .build() + notificationFactory.createDiagnosticNotification() ) } - - private fun getBitmap(context: Context, @DrawableRes drawableRes: Int): Bitmap? { - val drawable = ResourcesCompat.getDrawable(context.resources, drawableRes, null) ?: return null - val canvas = Canvas() - val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) - canvas.setBitmap(bitmap) - drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) - drawable.draw(canvas) - return bitmap - } - - /** - * Return true it the user has enabled the do not disturb mode. - */ - fun isDoNotDisturbModeOn(): Boolean { - // We cannot use NotificationManagerCompat here. - val setting = context.getSystemService()!!.currentInterruptionFilter - - return setting == NotificationManager.INTERRUPTION_FILTER_NONE || - setting == NotificationManager.INTERRUPTION_FILTER_ALARMS - } - - /* - private fun getActionText(@StringRes stringRes: Int, @AttrRes colorRes: Int): Spannable { - return SpannableString(context.getText(stringRes)).apply { - val foregroundColorSpan = ForegroundColorSpan(ThemeUtils.getColor(context, colorRes)) - setSpan(foregroundColorSpan, 0, length, 0) - } - } - */ - - private fun ensureTitleNotEmpty(title: String?): CharSequence { - if (title.isNullOrBlank()) { - return buildMeta.applicationName - } - - return title - } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt index 1f308a8d40..63115c9ccb 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt @@ -22,6 +22,7 @@ import androidx.core.app.Person import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.impl.R +import io.element.android.libraries.push.impl.notifications.factories.NotificationFactory import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.services.toolbox.api.strings.StringProvider import me.gujun.android.span.Span @@ -32,7 +33,7 @@ import javax.inject.Inject class RoomGroupMessageCreator @Inject constructor( private val bitmapLoader: NotificationBitmapLoader, private val stringProvider: StringProvider, - private val notificationUtils: NotificationUtils + private val notificationFactory: NotificationFactory ) { fun createRoomMessage( @@ -76,7 +77,7 @@ class RoomGroupMessageCreator @Inject constructor( shouldBing = events.any { it.noisy } ) return RoomNotification.Message( - notificationUtils.buildMessagesListNotification( + notificationFactory.createMessagesListNotification( style, RoomEventGroupInfo( sessionId = sessionId, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt index ed0053e58c..a400c2b7a3 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt @@ -20,6 +20,7 @@ import android.app.Notification import androidx.core.app.NotificationCompat import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.impl.R +import io.element.android.libraries.push.impl.notifications.factories.NotificationFactory import io.element.android.services.toolbox.api.strings.StringProvider import javax.inject.Inject @@ -39,7 +40,7 @@ import javax.inject.Inject */ class SummaryGroupMessageCreator @Inject constructor( private val stringProvider: StringProvider, - private val notificationUtils: NotificationUtils + private val notificationFactory: NotificationFactory ) { fun createSummaryNotification( @@ -72,7 +73,7 @@ class SummaryGroupMessageCreator @Inject constructor( // TODO get latest event? .setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents)) return if (useCompleteNotificationFormat) { - notificationUtils.buildSummaryListNotification( + notificationFactory.createSummaryListNotification( sessionId, summaryInboxStyle, sumTitle, @@ -165,7 +166,7 @@ class SummaryGroupMessageCreator @Inject constructor( messageStr } } - return notificationUtils.buildSummaryListNotification( + return notificationFactory.createSummaryListNotification( sessionId = sessionId, style = null, compatSummary = privacyTitle, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt new file mode 100755 index 0000000000..1bd2e313a8 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationFactory.kt @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications.factories + +import android.app.Notification +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import androidx.annotation.DrawableRes +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId +import io.element.android.libraries.push.impl.R +import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo +import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels +import io.element.android.libraries.push.impl.notifications.factories.action.AcceptInvitationActionFactory +import io.element.android.libraries.push.impl.notifications.factories.action.MarkAsReadActionFactory +import io.element.android.libraries.push.impl.notifications.factories.action.QuickReplyActionFactory +import io.element.android.libraries.push.impl.notifications.factories.action.RejectInvitationActionFactory +import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent +import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent +import io.element.android.services.toolbox.api.strings.StringProvider +import javax.inject.Inject + +class NotificationFactory @Inject constructor( + @ApplicationContext private val context: Context, + private val notificationChannels: NotificationChannels, + private val stringProvider: StringProvider, + private val buildMeta: BuildMeta, + private val pendingIntentFactory: PendingIntentFactory, + private val markAsReadActionFactory: MarkAsReadActionFactory, + private val quickReplyActionFactory: QuickReplyActionFactory, + private val rejectInvitationActionFactory: RejectInvitationActionFactory, + private val acceptInvitationActionFactory: AcceptInvitationActionFactory, +) { + /** + * Create a notification for a Room. + */ + fun createMessagesListNotification( + messageStyle: NotificationCompat.MessagingStyle, + roomInfo: RoomEventGroupInfo, + threadId: ThreadId?, + largeIcon: Bitmap?, + lastMessageTimestamp: Long, + tickerText: String + ): Notification { + val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) + // Build the pending intent for when the notification is clicked + val openIntent = when { + threadId != null -> pendingIntentFactory.createOpenThreadPendingIntent(roomInfo, threadId) + else -> pendingIntentFactory.createOpenRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId) + } + + val smallIcon = R.drawable.ic_notification + + val channelId = notificationChannels.getChannelIdForMessage(roomInfo.shouldBing) + return NotificationCompat.Builder(context, channelId) + .setOnlyAlertOnce(roomInfo.isUpdated) + .setWhen(lastMessageTimestamp) + // MESSAGING_STYLE sets title and content for API 16 and above devices. + .setStyle(messageStyle) + // A category allows groups of notifications to be ranked and filtered – per user or system settings. + // For example, alarm notifications should display before promo notifications, or message from known contact + // that can be displayed in not disturb mode if white listed (the later will need compat28.x) + .setCategory(NotificationCompat.CATEGORY_MESSAGE) + // ID of the corresponding shortcut, for conversation features under API 30+ + .setShortcutId(roomInfo.roomId.value) + // Title for API < 16 devices. + .setContentTitle(roomInfo.roomDisplayName) + // Content for API < 16 devices. + .setContentText(stringProvider.getString(R.string.notification_new_messages)) + // Number of new notifications for API <24 (M and below) devices. + .setSubText( + stringProvider.getQuantityString( + R.plurals.notification_new_messages_for_room, + messageStyle.messages.size, + messageStyle.messages.size + ) + ) + // Auto-bundling is enabled for 4 or more notifications on API 24+ (N+) + // devices and all Wear devices. But we want a custom grouping, so we specify the groupID + .setGroup(roomInfo.sessionId.value) + // In order to avoid notification making sound twice (due to the summary notification) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) + .setSmallIcon(smallIcon) + // Set primary color (important for Wear 2.0 Notifications). + .setColor(accentColor) + // Sets priority for 25 and below. For 26 and above, 'priority' is deprecated for + // 'importance' which is set in the NotificationChannel. The integers representing + // 'priority' are different from 'importance', so make sure you don't mix them. + .apply { + if (roomInfo.shouldBing) { + // Compat + priority = NotificationCompat.PRIORITY_DEFAULT + /* + vectorPreferences.getNotificationRingTone()?.let { + setSound(it) + } + */ + setLights(accentColor, 500, 500) + } else { + priority = NotificationCompat.PRIORITY_LOW + } + + // Add actions and notification intents + // Mark room as read + addAction(markAsReadActionFactory.create(roomInfo)) + // Quick reply + if (!roomInfo.hasSmartReplyError) { + addAction(quickReplyActionFactory.create(roomInfo, threadId)) + } + if (openIntent != null) { + setContentIntent(openIntent) + } + if (largeIcon != null) { + setLargeIcon(largeIcon) + } + setDeleteIntent(pendingIntentFactory.createDismissRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId)) + } + .setTicker(tickerText) + .build() + } + + fun createRoomInvitationNotification( + inviteNotifiableEvent: InviteNotifiableEvent + ): Notification { + val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) + val smallIcon = R.drawable.ic_notification + val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy) + return NotificationCompat.Builder(context, channelId) + .setOnlyAlertOnce(true) + .setContentTitle(inviteNotifiableEvent.roomName ?: buildMeta.applicationName) + .setContentText(inviteNotifiableEvent.description) + .setGroup(inviteNotifiableEvent.sessionId.value) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) + .setSmallIcon(smallIcon) + .setColor(accentColor) + .addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent)) + .addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent)) + .apply { + /* + // Build the pending intent for when the notification is clicked + val contentIntent = HomeActivity.newIntent( + context, + firstStartMainActivity = true, + inviteNotificationRoomId = inviteNotifiableEvent.roomId + ) + contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that + contentIntent.data = createIgnoredUri(inviteNotifiableEvent.eventId) + setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, PendingIntentCompat.FLAG_IMMUTABLE)) + + */ + + if (inviteNotifiableEvent.noisy) { + // Compat + priority = NotificationCompat.PRIORITY_DEFAULT + /* + vectorPreferences.getNotificationRingTone()?.let { + setSound(it) + } + */ + setLights(accentColor, 500, 500) + } else { + priority = NotificationCompat.PRIORITY_LOW + } + setAutoCancel(true) + } + .build() + } + + fun createSimpleEventNotification( + simpleNotifiableEvent: SimpleNotifiableEvent, + ): Notification { + val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) + val smallIcon = R.drawable.ic_notification + + val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy) + return NotificationCompat.Builder(context, channelId) + .setOnlyAlertOnce(true) + .setContentTitle(buildMeta.applicationName) + .setContentText(simpleNotifiableEvent.description) + .setGroup(simpleNotifiableEvent.sessionId.value) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) + .setSmallIcon(smallIcon) + .setColor(accentColor) + .setAutoCancel(true) + .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent(simpleNotifiableEvent.sessionId, simpleNotifiableEvent.roomId)) + .apply { + if (simpleNotifiableEvent.noisy) { + // Compat + priority = NotificationCompat.PRIORITY_DEFAULT + /* + vectorPreferences.getNotificationRingTone()?.let { + setSound(it) + } + */ + setLights(accentColor, 500, 500) + } else { + priority = NotificationCompat.PRIORITY_LOW + } + setAutoCancel(true) + } + .build() + } + + /** + * Create the summary notification. + */ + fun createSummaryListNotification( + sessionId: SessionId, + style: NotificationCompat.InboxStyle?, + compatSummary: String, + noisy: Boolean, + lastMessageTimestamp: Long + ): Notification { + val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) + val smallIcon = R.drawable.ic_notification + val channelId = notificationChannels.getChannelIdForMessage(noisy) + return NotificationCompat.Builder(context, channelId) + .setOnlyAlertOnce(true) + // used in compat < N, after summary is built based on child notifications + .setWhen(lastMessageTimestamp) + .setStyle(style) + .setContentTitle(sessionId.value) + .setCategory(NotificationCompat.CATEGORY_MESSAGE) + .setSmallIcon(smallIcon) + // set content text to support devices running API level < 24 + .setContentText(compatSummary) + .setGroup(sessionId.value) + // set this notification as the summary for the group + .setGroupSummary(true) + .setColor(accentColor) + .apply { + if (noisy) { + // Compat + priority = NotificationCompat.PRIORITY_DEFAULT + /* + vectorPreferences.getNotificationRingTone()?.let { + setSound(it) + } + */ + setLights(accentColor, 500, 500) + } else { + // compat + priority = NotificationCompat.PRIORITY_LOW + } + } + .setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(sessionId)) + .setDeleteIntent(pendingIntentFactory.createDismissSummaryPendingIntent(sessionId)) + .build() + } + + fun createDiagnosticNotification(): Notification { + return NotificationCompat.Builder(context, notificationChannels.getChannelIdForTest()) + .setContentTitle(buildMeta.applicationName) + .setContentText(stringProvider.getString(R.string.notification_test_push_notification_content)) + .setSmallIcon(R.drawable.ic_notification) + .setLargeIcon(getBitmap(context, R.drawable.element_logo_green)) + .setColor(ContextCompat.getColor(context, R.color.notification_accent_color)) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setCategory(NotificationCompat.CATEGORY_STATUS) + .setAutoCancel(true) + .setContentIntent(pendingIntentFactory.createTestPendingIntent()) + .build() + } + + private fun getBitmap(context: Context, @DrawableRes drawableRes: Int): Bitmap? { + val drawable = ResourcesCompat.getDrawable(context.resources, drawableRes, null) ?: return null + val canvas = Canvas() + val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) + canvas.setBitmap(bitmap) + drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) + drawable.draw(canvas) + return bitmap + } +}