Browse Source

Merge pull request #3053 from element-hq/feature/bma/callSettings

Alert for incoming call even if notifications are disabled - WAITING FOR FINAL PRODUCT DECISION
pull/3111/head
Benoit Marty 3 months ago committed by GitHub
parent
commit
f6fe030d6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      appconfig/build.gradle.kts
  2. 6
      appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt
  3. 1
      changelog.d/3053.misc
  4. 1
      features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt
  5. 91
      libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannels.kt
  6. 10
      libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt
  7. 22
      libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt
  8. 21
      libraries/push/impl/src/main/res/values/colors.xml
  9. 4
      libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannelsTest.kt
  10. 30
      libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt
  11. 3
      tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_12,NEXUS_5,1.0,en].png
  12. 3
      tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_12,NEXUS_5,1.0,en].png

1
appconfig/build.gradle.kts

@ -28,6 +28,7 @@ anvil { @@ -28,6 +28,7 @@ anvil {
}
dependencies {
implementation(libs.androidx.annotationjvm)
implementation(libs.dagger)
implementation(projects.libraries.di)
implementation(projects.libraries.matrix.api)

6
appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt

@ -16,6 +16,9 @@ @@ -16,6 +16,9 @@
package io.element.android.appconfig
import android.graphics.Color
import androidx.annotation.ColorInt
object NotificationConfig {
// TODO EAx Implement and set to true at some point
const val SUPPORT_MARK_AS_READ_ACTION = false
@ -25,4 +28,7 @@ object NotificationConfig { @@ -25,4 +28,7 @@ object NotificationConfig {
// TODO EAx Implement and set to true at some point
const val SUPPORT_QUICK_REPLY_ACTION = false
@ColorInt
val NOTIFICATION_ACCENT_COLOR: Int = Color.parseColor("#FF0DBD8B")
}

1
changelog.d/3053.misc

@ -0,0 +1 @@ @@ -0,0 +1 @@
Alert for incoming call even if notifications are disabled

1
features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt

@ -42,6 +42,7 @@ open class NotificationSettingsStateProvider : PreviewParameterProvider<Notifica @@ -42,6 +42,7 @@ open class NotificationSettingsStateProvider : PreviewParameterProvider<Notifica
aInvalidNotificationSettingsState(),
aInvalidNotificationSettingsState(fixFailed = true),
aValidNotificationSettingsState(fullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(permissionGranted = false)),
aValidNotificationSettingsState(appNotificationEnabled = false),
)
}

91
libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannels.kt

@ -16,8 +16,6 @@ @@ -16,8 +16,6 @@
package io.element.android.libraries.push.impl.notifications.channels
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioManager
@ -26,8 +24,8 @@ import android.os.Build @@ -26,8 +24,8 @@ import android.os.Build
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.NotificationConfig
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.SingleIn
@ -38,14 +36,9 @@ import javax.inject.Inject @@ -38,14 +36,9 @@ import javax.inject.Inject
/* ==========================================================================================
* IDs for channels
* ========================================================================================== */
private const val LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID = "LISTEN_FOR_EVENTS_NOTIFICATION_CHANNEL_ID"
internal const val SILENT_NOTIFICATION_CHANNEL_ID = "DEFAULT_SILENT_NOTIFICATION_CHANNEL_ID_V2"
internal const val NOISY_NOTIFICATION_CHANNEL_ID = "DEFAULT_NOISY_NOTIFICATION_CHANNEL_ID"
// Legacy channel
private const val CALL_NOTIFICATION_CHANNEL_ID_V2 = "CALL_NOTIFICATION_CHANNEL_ID_V2"
internal const val CALL_NOTIFICATION_CHANNEL_ID_V3 = "CALL_NOTIFICATION_CHANNEL_ID_V3"
internal const val CALL_NOTIFICATION_CHANNEL_ID = "CALL_NOTIFICATION_CHANNEL_ID_V3"
internal const val RINGING_CALL_NOTIFICATION_CHANNEL_ID = "RINGING_CALL_NOTIFICATION_CHANNEL_ID"
/**
@ -96,7 +89,7 @@ class DefaultNotificationChannels @Inject constructor( @@ -96,7 +89,7 @@ class DefaultNotificationChannels @Inject constructor(
return
}
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val accentColor = NotificationConfig.NOTIFICATION_ACCENT_COLOR
// Migration - the noisy channel was deleted and recreated when sound preference was changed (id was DEFAULT_NOISY_NOTIFICATION_CHANNEL_ID_BASE
// + currentTimeMillis).
@ -110,76 +103,62 @@ class DefaultNotificationChannels @Inject constructor( @@ -110,76 +103,62 @@ class DefaultNotificationChannels @Inject constructor(
}
}
// Migration - Remove deprecated channels
for (channelId in listOf("DEFAULT_SILENT_NOTIFICATION_CHANNEL_ID", "CALL_NOTIFICATION_CHANNEL_ID")) {
for (channelId in listOf(
"DEFAULT_SILENT_NOTIFICATION_CHANNEL_ID",
"CALL_NOTIFICATION_CHANNEL_ID",
"CALL_NOTIFICATION_CHANNEL_ID_V2",
"LISTEN_FOR_EVENTS_NOTIFICATION_CHANNEL_ID",
)) {
notificationManager.getNotificationChannel(channelId)?.let {
notificationManager.deleteNotificationChannel(channelId)
}
}
// Migration - Create new call channel
notificationManager.deleteNotificationChannel(CALL_NOTIFICATION_CHANNEL_ID_V2)
/**
* Default notification importance: shows everywhere, makes noise, but does not visually
* intrude.
*/
notificationManager.createNotificationChannel(
NotificationChannel(
NotificationChannelCompat.Builder(
NOISY_NOTIFICATION_CHANNEL_ID,
stringProvider.getString(R.string.notification_channel_noisy).ifEmpty { "Noisy notifications" },
NotificationManager.IMPORTANCE_DEFAULT
NotificationManagerCompat.IMPORTANCE_DEFAULT
)
.apply {
description = stringProvider.getString(R.string.notification_channel_noisy)
enableVibration(true)
enableLights(true)
lightColor = accentColor
}
.setName(stringProvider.getString(R.string.notification_channel_noisy).ifEmpty { "Noisy notifications" })
.setDescription(stringProvider.getString(R.string.notification_channel_noisy))
.setVibrationEnabled(true)
.setLightsEnabled(true)
.setLightColor(accentColor)
.build()
)
/**
* Low notification importance: shows everywhere, but is not intrusive.
*/
notificationManager.createNotificationChannel(
NotificationChannel(
NotificationChannelCompat.Builder(
SILENT_NOTIFICATION_CHANNEL_ID,
stringProvider.getString(R.string.notification_channel_silent).ifEmpty { "Silent notifications" },
NotificationManager.IMPORTANCE_LOW
NotificationManagerCompat.IMPORTANCE_LOW
)
.apply {
description = stringProvider.getString(R.string.notification_channel_silent)
setSound(null, null)
enableLights(true)
lightColor = accentColor
}
)
notificationManager.createNotificationChannel(
NotificationChannel(
LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID,
stringProvider.getString(R.string.notification_channel_listening_for_events).ifEmpty { "Listening for events" },
NotificationManager.IMPORTANCE_MIN
)
.apply {
description = stringProvider.getString(R.string.notification_channel_listening_for_events)
setSound(null, null)
setShowBadge(false)
}
.setName(stringProvider.getString(R.string.notification_channel_silent).ifEmpty { "Silent notifications" })
.setDescription(stringProvider.getString(R.string.notification_channel_silent))
.setSound(null, null)
.setLightsEnabled(true)
.setLightColor(accentColor)
.build()
)
// Register a channel for incoming and in progress call notifications with no ringing
notificationManager.createNotificationChannel(
NotificationChannel(
CALL_NOTIFICATION_CHANNEL_ID_V3,
stringProvider.getString(R.string.notification_channel_call).ifEmpty { "Call" },
NotificationManager.IMPORTANCE_HIGH
NotificationChannelCompat.Builder(
CALL_NOTIFICATION_CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_HIGH
)
.apply {
description = stringProvider.getString(R.string.notification_channel_call)
enableVibration(true)
enableLights(true)
lightColor = accentColor
}
.setName(stringProvider.getString(R.string.notification_channel_call).ifEmpty { "Call" })
.setDescription(stringProvider.getString(R.string.notification_channel_call))
.setVibrationEnabled(true)
.setLightsEnabled(true)
.setLightColor(accentColor)
.build()
)
// Register a channel for incoming call notifications which will ring the device when received
@ -207,7 +186,7 @@ class DefaultNotificationChannels @Inject constructor( @@ -207,7 +186,7 @@ class DefaultNotificationChannels @Inject constructor(
}
override fun getChannelForIncomingCall(ring: Boolean): String {
return if (ring) RINGING_CALL_NOTIFICATION_CHANNEL_ID else CALL_NOTIFICATION_CHANNEL_ID_V3
return if (ring) RINGING_CALL_NOTIFICATION_CHANNEL_ID else CALL_NOTIFICATION_CHANNEL_ID
}
override fun getChannelIdForMessage(noisy: Boolean): String {

10
libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt

@ -24,7 +24,6 @@ import androidx.annotation.DrawableRes @@ -24,7 +24,6 @@ import androidx.annotation.DrawableRes
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.MessagingStyle
import androidx.core.app.Person
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import coil.ImageLoader
import com.squareup.anvil.annotations.ContributesBinding
@ -107,6 +106,8 @@ class DefaultNotificationCreator @Inject constructor( @@ -107,6 +106,8 @@ class DefaultNotificationCreator @Inject constructor(
private val acceptInvitationActionFactory: AcceptInvitationActionFactory,
private val rejectInvitationActionFactory: RejectInvitationActionFactory
) : NotificationCreator {
private val accentColor = NotificationConfig.NOTIFICATION_ACCENT_COLOR
/**
* Create a notification for a Room.
*/
@ -121,7 +122,6 @@ class DefaultNotificationCreator @Inject constructor( @@ -121,7 +122,6 @@ class DefaultNotificationCreator @Inject constructor(
imageLoader: ImageLoader,
events: List<NotifiableMessageEvent>,
): 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)
@ -228,7 +228,6 @@ class DefaultNotificationCreator @Inject constructor( @@ -228,7 +228,6 @@ class DefaultNotificationCreator @Inject constructor(
override fun createRoomInvitationNotification(
inviteNotifiableEvent: InviteNotifiableEvent
): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val smallIcon = CommonDrawables.ic_notification_small
val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy)
return NotificationCompat.Builder(context, channelId)
@ -273,7 +272,6 @@ class DefaultNotificationCreator @Inject constructor( @@ -273,7 +272,6 @@ class DefaultNotificationCreator @Inject constructor(
override fun createSimpleEventNotification(
simpleNotifiableEvent: SimpleNotifiableEvent,
): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val smallIcon = CommonDrawables.ic_notification_small
val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy)
@ -307,7 +305,6 @@ class DefaultNotificationCreator @Inject constructor( @@ -307,7 +305,6 @@ class DefaultNotificationCreator @Inject constructor(
override fun createFallbackNotification(
fallbackNotifiableEvent: FallbackNotifiableEvent,
): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val smallIcon = CommonDrawables.ic_notification_small
val channelId = notificationChannels.getChannelIdForMessage(false)
@ -344,7 +341,6 @@ class DefaultNotificationCreator @Inject constructor( @@ -344,7 +341,6 @@ class DefaultNotificationCreator @Inject constructor(
noisy: Boolean,
lastMessageTimestamp: Long
): Notification {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
val smallIcon = CommonDrawables.ic_notification_small
val channelId = notificationChannels.getChannelIdForMessage(noisy)
return NotificationCompat.Builder(context, channelId)
@ -384,7 +380,7 @@ class DefaultNotificationCreator @Inject constructor( @@ -384,7 +380,7 @@ class DefaultNotificationCreator @Inject constructor(
.setContentText(stringProvider.getString(R.string.notification_test_push_notification_content))
.setSmallIcon(CommonDrawables.ic_notification_small)
.setLargeIcon(getBitmap(R.drawable.element_logo_green))
.setColor(ContextCompat.getColor(context, R.color.notification_accent_color))
.setColor(accentColor)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setAutoCancel(true)

22
libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt

@ -96,17 +96,19 @@ class DefaultPushHandler @Inject constructor( @@ -96,17 +96,19 @@ class DefaultPushHandler @Inject constructor(
Timber.w("Unable to get a session")
return
}
val userPushStore = userPushStoreFactory.getOrCreate(userId)
val areNotificationsEnabled = userPushStore.getNotificationEnabledForDevice().first()
if (areNotificationsEnabled) {
val notifiableEvent = notifiableEventResolver.resolveEvent(userId, pushData.roomId, pushData.eventId)
when (notifiableEvent) {
null -> Timber.tag(loggerTag.value).w("Unable to get a notification data")
is NotifiableRingingCallEvent -> handleRingingCallEvent(notifiableEvent)
else -> onNotifiableEventReceived.onNotifiableEventReceived(notifiableEvent)
val notifiableEvent = notifiableEventResolver.resolveEvent(userId, pushData.roomId, pushData.eventId)
when (notifiableEvent) {
null -> Timber.tag(loggerTag.value).w("Unable to get a notification data")
is NotifiableRingingCallEvent -> handleRingingCallEvent(notifiableEvent)
else -> {
val userPushStore = userPushStoreFactory.getOrCreate(userId)
val areNotificationsEnabled = userPushStore.getNotificationEnabledForDevice().first()
if (areNotificationsEnabled) {
onNotifiableEventReceived.onNotifiableEventReceived(notifiableEvent)
} else {
Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.")
}
}
} else {
Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.")
}
} catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## handleInternal() failed")

21
libraries/push/impl/src/main/res/values/colors.xml

@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<resources>
<color name="notification_accent_color">#368BD6</color>
</resources>

4
libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannelsTest.kt

@ -16,7 +16,6 @@ @@ -16,7 +16,6 @@
package io.element.android.libraries.push.impl.notifications.channels
import android.app.NotificationChannel
import android.os.Build
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationManagerCompat
@ -43,7 +42,6 @@ class NotificationChannelsTest { @@ -43,7 +42,6 @@ class NotificationChannelsTest {
createNotificationChannels(notificationManager = notificationManager)
verify { notificationManager.createNotificationChannel(any<NotificationChannelCompat>()) }
verify { notificationManager.createNotificationChannel(any<NotificationChannel>()) }
verify { notificationManager.deleteNotificationChannel(any<String>()) }
}
@ -55,7 +53,7 @@ class NotificationChannelsTest { @@ -55,7 +53,7 @@ class NotificationChannelsTest {
assertThat(ringingChannel).isEqualTo(RINGING_CALL_NOTIFICATION_CHANNEL_ID)
val normalChannel = notificationChannels.getChannelForIncomingCall(ring = false)
assertThat(normalChannel).isEqualTo(CALL_NOTIFICATION_CHANNEL_ID_V3)
assertThat(normalChannel).isEqualTo(CALL_NOTIFICATION_CHANNEL_ID)
}
@Test

30
libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt

@ -118,7 +118,7 @@ class DefaultPushHandlerTest { @@ -118,7 +118,7 @@ class DefaultPushHandlerTest {
incrementPushCounterResult.assertions()
.isCalledOnce()
notifiableEventResult.assertions()
.isNeverCalled()
.isCalledOnce()
onNotifiableEventReceived.assertions()
.isNeverCalled()
}
@ -277,6 +277,34 @@ class DefaultPushHandlerTest { @@ -277,6 +277,34 @@ class DefaultPushHandlerTest {
onNotifiableEventReceived.assertions().isCalledOnce()
}
@Test
fun `when notify call PushData is received, the incoming call will be treated as a normal notification even if notification are disabled`() = runTest {
val aPushData = PushData(
eventId = AN_EVENT_ID,
roomId = A_ROOM_ID,
unread = 0,
clientSecret = A_SECRET,
)
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
val handleIncomingCallLambda = lambdaRecorder<CallType.RoomCall, EventId, UserId, String?, String?, String?, String, Unit> { _, _, _, _, _, _, _ -> }
val elementCallEntryPoint = FakeElementCallEntryPoint(handleIncomingCallResult = handleIncomingCallLambda)
val defaultPushHandler = createDefaultPushHandler(
elementCallEntryPoint = elementCallEntryPoint,
onNotifiableEventReceived = onNotifiableEventReceived,
notifiableEventResult = { _, _, _ -> aNotifiableCallEvent() },
incrementPushCounterResult = {},
userPushStore = FakeUserPushStore().apply {
setNotificationEnabledForDevice(false)
},
pushClientSecret = FakePushClientSecret(
getUserIdFromSecretResult = { A_USER_ID }
),
)
defaultPushHandler.handle(aPushData)
handleIncomingCallLambda.assertions().isCalledOnce()
onNotifiableEventReceived.assertions().isNeverCalled()
}
@Test
fun `when diagnostic PushData is received, the diagnostic push handler is informed `() =
runTest {

3
tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_12,NEXUS_5,1.0,en].png

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b97743618b94a6f0e9f3e9d1e776f818deafb8e1bfb9671e85497359b2d45a95
size 15210

3
tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_12,NEXUS_5,1.0,en].png

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ea1f406cdc673d242f6a8920da2f15316961f9765ddb43e189ef1c057417658a
size 14762
Loading…
Cancel
Save