Merge pull request #6177 from element-hq/feature/bma/notificationCustomSound
Let enterprise build be able to use a different notification channel for noisy notification.
This commit is contained in:
Submodule enterprise updated: 6207ddc1cb...1fd0d297d9
@@ -35,6 +35,11 @@ interface EnterpriseService {
|
||||
|
||||
fun bugReportUrlFlow(sessionId: SessionId?): Flow<BugReportUrl>
|
||||
|
||||
/**
|
||||
* Gets Notification Channel to use for the noisy notifications of the provided session.
|
||||
*/
|
||||
fun getNoisyNotificationChannelId(sessionId: SessionId): String?
|
||||
|
||||
companion object {
|
||||
const val ANY_ACCOUNT_PROVIDER = "*"
|
||||
}
|
||||
|
||||
@@ -43,4 +43,6 @@ class DefaultEnterpriseService : EnterpriseService {
|
||||
override fun bugReportUrlFlow(sessionId: SessionId?): Flow<BugReportUrl> {
|
||||
return flowOf(BugReportUrl.UseDefault)
|
||||
}
|
||||
|
||||
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? = null
|
||||
}
|
||||
|
||||
@@ -98,4 +98,10 @@ class DefaultEnterpriseServiceTest {
|
||||
awaitComplete()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getNoisyNotificationChannelId returns null`() = runTest {
|
||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||
assertThat(defaultEnterpriseService.getNoisyNotificationChannelId(A_SESSION_ID)).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class FakeEnterpriseService(
|
||||
private val overrideBrandColorResult: (SessionId?, String?) -> Unit = { _, _ -> lambdaError() },
|
||||
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
|
||||
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
|
||||
private val getNoisyNotificationChannelIdResult: (SessionId?) -> String? = { lambdaError() },
|
||||
) : EnterpriseService {
|
||||
private val brandColorState = MutableStateFlow(initialBrandColor)
|
||||
private val semanticColorsState = MutableStateFlow(initialSemanticColors)
|
||||
@@ -69,4 +70,8 @@ class FakeEnterpriseService(
|
||||
override fun bugReportUrlFlow(sessionId: SessionId?): Flow<BugReportUrl> {
|
||||
return bugReportUrlMutableFlow.asStateFlow()
|
||||
}
|
||||
|
||||
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? {
|
||||
return getNoisyNotificationChannelIdResult(sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -23,7 +23,9 @@ import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.appconfig.NotificationConfig
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
|
||||
@@ -47,9 +49,10 @@ interface NotificationChannels {
|
||||
|
||||
/**
|
||||
* Get the channel for messages.
|
||||
* @param sessionId the session the message belongs to.
|
||||
* @param noisy true if the notification should have sound and vibration.
|
||||
*/
|
||||
fun getChannelIdForMessage(noisy: Boolean): String
|
||||
fun getChannelIdForMessage(sessionId: SessionId, noisy: Boolean): String
|
||||
|
||||
/**
|
||||
* Get the channel for test notifications.
|
||||
@@ -67,6 +70,7 @@ class DefaultNotificationChannels(
|
||||
private val stringProvider: StringProvider,
|
||||
@ApplicationContext
|
||||
private val context: Context,
|
||||
private val enterpriseService: EnterpriseService,
|
||||
) : NotificationChannels {
|
||||
init {
|
||||
createNotificationChannels()
|
||||
@@ -115,7 +119,7 @@ class DefaultNotificationChannels(
|
||||
.setSound(
|
||||
Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
|
||||
// Strangely wwe have to provide a "//" before the package name
|
||||
// Strangely we have to provide a "//" before the package name
|
||||
.path("//" + context.packageName + "/" + R.raw.message)
|
||||
.build(),
|
||||
AudioAttributes.Builder()
|
||||
@@ -186,8 +190,13 @@ class DefaultNotificationChannels(
|
||||
return if (ring) RINGING_CALL_NOTIFICATION_CHANNEL_ID else CALL_NOTIFICATION_CHANNEL_ID
|
||||
}
|
||||
|
||||
override fun getChannelIdForMessage(noisy: Boolean): String {
|
||||
return if (noisy) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID
|
||||
override fun getChannelIdForMessage(sessionId: SessionId, noisy: Boolean): String {
|
||||
return if (noisy) {
|
||||
enterpriseService.getNoisyNotificationChannelId(sessionId)
|
||||
?: NOISY_NOTIFICATION_CHANNEL_ID
|
||||
} else {
|
||||
SILENT_NOTIFICATION_CHANNEL_ID
|
||||
}
|
||||
}
|
||||
|
||||
override fun getChannelIdForTest(): String = NOISY_NOTIFICATION_CHANNEL_ID
|
||||
|
||||
@@ -152,7 +152,10 @@ class DefaultNotificationCreator(
|
||||
val channelId = if (containsMissedCall) {
|
||||
notificationChannels.getChannelForIncomingCall(false)
|
||||
} else {
|
||||
notificationChannels.getChannelIdForMessage(noisy = roomInfo.shouldBing)
|
||||
notificationChannels.getChannelIdForMessage(
|
||||
sessionId = roomInfo.sessionId,
|
||||
noisy = roomInfo.shouldBing,
|
||||
)
|
||||
}
|
||||
// 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
|
||||
@@ -231,7 +234,10 @@ class DefaultNotificationCreator(
|
||||
notificationAccountParams: NotificationAccountParams,
|
||||
inviteNotifiableEvent: InviteNotifiableEvent,
|
||||
): Notification {
|
||||
val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy)
|
||||
val channelId = notificationChannels.getChannelIdForMessage(
|
||||
sessionId = inviteNotifiableEvent.sessionId,
|
||||
noisy = inviteNotifiableEvent.noisy,
|
||||
)
|
||||
return NotificationCompat.Builder(context, channelId)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentTitle((inviteNotifiableEvent.roomName ?: buildMeta.applicationName).annotateForDebug(5))
|
||||
@@ -271,7 +277,10 @@ class DefaultNotificationCreator(
|
||||
notificationAccountParams: NotificationAccountParams,
|
||||
simpleNotifiableEvent: SimpleNotifiableEvent,
|
||||
): Notification {
|
||||
val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy)
|
||||
val channelId = notificationChannels.getChannelIdForMessage(
|
||||
sessionId = simpleNotifiableEvent.sessionId,
|
||||
noisy = simpleNotifiableEvent.noisy,
|
||||
)
|
||||
return NotificationCompat.Builder(context, channelId)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentTitle(buildMeta.applicationName.annotateForDebug(7))
|
||||
@@ -304,13 +313,16 @@ class DefaultNotificationCreator(
|
||||
notificationAccountParams: NotificationAccountParams,
|
||||
fallbackNotifiableEvents: List<FallbackNotifiableEvent>,
|
||||
): Notification {
|
||||
val channelId = notificationChannels.getChannelIdForMessage(false)
|
||||
val fallbackNotifiableEvent = fallbackNotifiableEvents.first()
|
||||
val channelId = notificationChannels.getChannelIdForMessage(
|
||||
sessionId = fallbackNotifiableEvent.sessionId,
|
||||
noisy = false,
|
||||
)
|
||||
val existingCounter = existingNotification
|
||||
?.extras
|
||||
?.getInt(FALLBACK_COUNTER_EXTRA)
|
||||
?: 0
|
||||
val counter = existingCounter + fallbackNotifiableEvents.size
|
||||
val fallbackNotifiableEvent = fallbackNotifiableEvents.first()
|
||||
return NotificationCompat.Builder(context, channelId)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentTitle(buildMeta.applicationName.annotateForDebug(7))
|
||||
@@ -342,8 +354,11 @@ class DefaultNotificationCreator(
|
||||
noisy: Boolean,
|
||||
lastMessageTimestamp: Long,
|
||||
): Notification {
|
||||
val channelId = notificationChannels.getChannelIdForMessage(noisy)
|
||||
val userId = notificationAccountParams.user.userId
|
||||
val channelId = notificationChannels.getChannelIdForMessage(
|
||||
sessionId = userId,
|
||||
noisy = noisy,
|
||||
)
|
||||
return NotificationCompat.Builder(context, channelId)
|
||||
.setOnlyAlertOnce(true)
|
||||
// used in compat < N, after summary is built based on child notifications
|
||||
|
||||
@@ -13,6 +13,8 @@ import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appconfig.NotificationConfig
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_TIMESTAMP
|
||||
@@ -66,7 +68,11 @@ class DefaultBaseRoomGroupMessageCreatorTest {
|
||||
|
||||
@Test
|
||||
fun `test createRoomMessage with one noisy Event`() = runTest {
|
||||
val sut = createRoomGroupMessageCreator()
|
||||
val sut = createRoomGroupMessageCreator(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
getNoisyNotificationChannelIdResult = { null }
|
||||
)
|
||||
)
|
||||
val fakeImageLoader = FakeImageLoader()
|
||||
val result = sut.createRoomMessage(
|
||||
notificationAccountParams = aNotificationAccountParams(),
|
||||
@@ -228,6 +234,7 @@ class DefaultBaseRoomGroupMessageCreatorTest {
|
||||
|
||||
fun createRoomGroupMessageCreator(
|
||||
sdkIntProvider: BuildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.O),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
): RoomGroupMessageCreator {
|
||||
val context = RuntimeEnvironment.getApplication() as Context
|
||||
val bitmapLoader = DefaultNotificationBitmapLoader(
|
||||
@@ -236,7 +243,10 @@ fun createRoomGroupMessageCreator(
|
||||
initialsAvatarBitmapGenerator = FakeInitialsAvatarBitmapGenerator(),
|
||||
)
|
||||
return DefaultRoomGroupMessageCreator(
|
||||
notificationCreator = createNotificationCreator(bitmapLoader = bitmapLoader),
|
||||
notificationCreator = createNotificationCreator(
|
||||
bitmapLoader = bitmapLoader,
|
||||
enterpriseService = enterpriseService,
|
||||
),
|
||||
bitmapLoader = bitmapLoader,
|
||||
stringProvider = AndroidStringProvider(context.resources)
|
||||
)
|
||||
|
||||
@@ -8,17 +8,19 @@
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications.channels
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
class FakeNotificationChannels(
|
||||
var channelForIncomingCall: (ring: Boolean) -> String = { _ -> "" },
|
||||
var channelIdForMessage: (noisy: Boolean) -> String = { _ -> "" },
|
||||
var channelIdForMessage: (sessionId: SessionId, noisy: Boolean) -> String = { _, _ -> "" },
|
||||
var channelIdForTest: () -> String = { "" }
|
||||
) : NotificationChannels {
|
||||
override fun getChannelForIncomingCall(ring: Boolean): String {
|
||||
return channelForIncomingCall(ring)
|
||||
}
|
||||
|
||||
override fun getChannelIdForMessage(noisy: Boolean): String {
|
||||
return channelIdForMessage(noisy)
|
||||
override fun getChannelIdForMessage(sessionId: SessionId, noisy: Boolean): String {
|
||||
return channelIdForMessage(sessionId, noisy)
|
||||
}
|
||||
|
||||
override fun getChannelIdForTest(): String {
|
||||
|
||||
@@ -12,6 +12,9 @@ import android.os.Build
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
@@ -50,10 +53,28 @@ class NotificationChannelsTest {
|
||||
|
||||
@Test
|
||||
fun `getChannelIdForMessage - returns the right channel`() {
|
||||
val notificationChannels = createNotificationChannels()
|
||||
val notificationChannels = createNotificationChannels(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
getNoisyNotificationChannelIdResult = { null }
|
||||
),
|
||||
)
|
||||
assertThat(notificationChannels.getChannelIdForMessage(sessionId = A_SESSION_ID, noisy = true))
|
||||
.isEqualTo(NOISY_NOTIFICATION_CHANNEL_ID)
|
||||
assertThat(notificationChannels.getChannelIdForMessage(sessionId = A_SESSION_ID, noisy = false))
|
||||
.isEqualTo(SILENT_NOTIFICATION_CHANNEL_ID)
|
||||
}
|
||||
|
||||
assertThat(notificationChannels.getChannelIdForMessage(noisy = true)).isEqualTo(NOISY_NOTIFICATION_CHANNEL_ID)
|
||||
assertThat(notificationChannels.getChannelIdForMessage(noisy = false)).isEqualTo(SILENT_NOTIFICATION_CHANNEL_ID)
|
||||
@Test
|
||||
fun `getChannelIdForMessage - returns the right channel when enterprise service override the result`() {
|
||||
val notificationChannels = createNotificationChannels(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
getNoisyNotificationChannelIdResult = { "A_CHANNEL_ID" }
|
||||
),
|
||||
)
|
||||
assertThat(notificationChannels.getChannelIdForMessage(sessionId = A_SESSION_ID, noisy = true))
|
||||
.isEqualTo("A_CHANNEL_ID")
|
||||
assertThat(notificationChannels.getChannelIdForMessage(sessionId = A_SESSION_ID, noisy = false))
|
||||
.isEqualTo(SILENT_NOTIFICATION_CHANNEL_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -65,9 +86,11 @@ class NotificationChannelsTest {
|
||||
|
||||
private fun createNotificationChannels(
|
||||
notificationManager: NotificationManagerCompat = mockk(relaxed = true),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
) = DefaultNotificationChannels(
|
||||
notificationManager = notificationManager,
|
||||
stringProvider = FakeStringProvider(),
|
||||
context = RuntimeEnvironment.getApplication(),
|
||||
enterpriseService = enterpriseService,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appconfig.NotificationConfig
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_COLOR_INT
|
||||
@@ -121,7 +123,11 @@ class DefaultNotificationCreatorTest {
|
||||
|
||||
@Test
|
||||
fun `test createSimpleEventNotification noisy`() {
|
||||
val sut = createNotificationCreator()
|
||||
val sut = createNotificationCreator(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
getNoisyNotificationChannelIdResult = { null },
|
||||
),
|
||||
)
|
||||
val result = sut.createSimpleEventNotification(
|
||||
notificationAccountParams = aNotificationAccountParams(),
|
||||
SimpleNotifiableEvent(
|
||||
@@ -181,7 +187,11 @@ class DefaultNotificationCreatorTest {
|
||||
|
||||
@Test
|
||||
fun `test createRoomInvitationNotification noisy`() {
|
||||
val sut = createNotificationCreator()
|
||||
val sut = createNotificationCreator(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
getNoisyNotificationChannelIdResult = { null },
|
||||
),
|
||||
)
|
||||
val result = sut.createRoomInvitationNotification(
|
||||
notificationAccountParams = aNotificationAccountParams(),
|
||||
InviteNotifiableEvent(
|
||||
@@ -223,7 +233,11 @@ class DefaultNotificationCreatorTest {
|
||||
|
||||
@Test
|
||||
fun `test createSummaryListNotification noisy`() {
|
||||
val sut = createNotificationCreator()
|
||||
val sut = createNotificationCreator(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
getNoisyNotificationChannelIdResult = { null },
|
||||
),
|
||||
)
|
||||
val matrixUser = aMatrixUser()
|
||||
val result = sut.createSummaryListNotification(
|
||||
notificationAccountParams = aNotificationAccountParams(user = matrixUser),
|
||||
@@ -263,7 +277,11 @@ class DefaultNotificationCreatorTest {
|
||||
|
||||
@Test
|
||||
fun `test createMessagesListNotification should bing and thread`() = runTest {
|
||||
val sut = createNotificationCreator()
|
||||
val sut = createNotificationCreator(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
getNoisyNotificationChannelIdResult = { null },
|
||||
),
|
||||
)
|
||||
val result = sut.createMessagesListNotification(
|
||||
notificationAccountParams = aNotificationAccountParams(),
|
||||
roomInfo = RoomEventGroupInfo(
|
||||
@@ -304,7 +322,8 @@ const val REJECT_INVITATION_ACTION_TITLE = "RejectInvitationAction"
|
||||
fun createNotificationCreator(
|
||||
context: Context = RuntimeEnvironment.getApplication(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
notificationChannels: NotificationChannels = createNotificationChannels(),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
notificationChannels: NotificationChannels = createNotificationChannels(enterpriseService),
|
||||
bitmapLoader: NotificationBitmapLoader = DefaultNotificationBitmapLoader(
|
||||
context = context,
|
||||
sdkIntProvider = FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.R),
|
||||
@@ -350,11 +369,14 @@ fun createNotificationCreator(
|
||||
)
|
||||
}
|
||||
|
||||
fun createNotificationChannels(): NotificationChannels {
|
||||
fun createNotificationChannels(
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
): NotificationChannels {
|
||||
val context = RuntimeEnvironment.getApplication()
|
||||
return DefaultNotificationChannels(
|
||||
notificationManager = NotificationManagerCompat.from(context),
|
||||
stringProvider = FakeStringProvider(""),
|
||||
context = context,
|
||||
enterpriseService = enterpriseService,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ data class ElementWellKnown(
|
||||
val enforceElementPro: Boolean?,
|
||||
val rageshakeUrl: String?,
|
||||
val brandColor: String?,
|
||||
val notificationSound: String?,
|
||||
)
|
||||
|
||||
@@ -30,4 +30,6 @@ data class InternalElementWellKnown(
|
||||
val rageshakeUrl: String? = null,
|
||||
@SerialName("brand_color")
|
||||
val brandColor: String? = null,
|
||||
@SerialName("notification_sound")
|
||||
val notificationSound: String? = null,
|
||||
)
|
||||
|
||||
@@ -15,4 +15,5 @@ internal fun InternalElementWellKnown.map() = ElementWellKnown(
|
||||
enforceElementPro = enforceElementPro,
|
||||
rageshakeUrl = rageshakeUrl,
|
||||
brandColor = brandColor,
|
||||
notificationSound = notificationSound,
|
||||
)
|
||||
|
||||
@@ -35,6 +35,7 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
enforceElementPro = null,
|
||||
rageshakeUrl = null,
|
||||
brandColor = null,
|
||||
notificationSound = null,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -51,7 +52,8 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
"registration_helper_url": "a_registration_url",
|
||||
"enforce_element_pro": true,
|
||||
"rageshake_url": "a_rageshake_url",
|
||||
"brand_color": "#FF0000"
|
||||
"brand_color": "#FF0000",
|
||||
"notification_sound": "a_notification_sound.flac"
|
||||
}""".trimIndent().toByteArray()
|
||||
)
|
||||
}
|
||||
@@ -63,6 +65,7 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
enforceElementPro = true,
|
||||
rageshakeUrl = "a_rageshake_url",
|
||||
brandColor = "#FF0000",
|
||||
notificationSound = "a_notification_sound.flac",
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -89,6 +92,7 @@ class DefaultSessionWellknownRetrieverTest {
|
||||
enforceElementPro = true,
|
||||
rageshakeUrl = "a_rageshake_url",
|
||||
brandColor = null,
|
||||
notificationSound = null,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -15,9 +15,11 @@ fun anElementWellKnown(
|
||||
enforceElementPro: Boolean? = null,
|
||||
rageshakeUrl: String? = null,
|
||||
brandColor: String? = null,
|
||||
notificationSound: String? = null,
|
||||
) = ElementWellKnown(
|
||||
registrationHelperUrl = registrationHelperUrl,
|
||||
enforceElementPro = enforceElementPro,
|
||||
rageshakeUrl = rageshakeUrl,
|
||||
brandColor = brandColor,
|
||||
notificationSound = notificationSound,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user