diff --git a/app/src/main/kotlin/io/element/android/x/intent/DefaultIntentProvider.kt b/app/src/main/kotlin/io/element/android/x/intent/DefaultIntentProvider.kt index a4272063ba..0453fdf160 100644 --- a/app/src/main/kotlin/io/element/android/x/intent/DefaultIntentProvider.kt +++ b/app/src/main/kotlin/io/element/android/x/intent/DefaultIntentProvider.kt @@ -10,6 +10,7 @@ package io.element.android.x.intent import android.content.Context import android.content.Intent +import android.os.Bundle import androidx.core.net.toUri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding @@ -32,11 +33,12 @@ class DefaultIntentProvider( roomId: RoomId?, threadId: ThreadId?, eventId: EventId?, + extras: Bundle?, ): Intent { return Intent(context, MainActivity::class.java).apply { action = Intent.ACTION_VIEW data = deepLinkCreator.create(sessionId, roomId, threadId, eventId).toUri() - putExtra("from_notification", true) + extras?.let(::putExtras) } } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 838804a055..4687946367 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -29,7 +29,6 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.annotations.ContributesNode -import io.element.android.appnav.analytics.AnalyticsColdStartWatcher import io.element.android.appnav.di.MatrixSessionCache import io.element.android.appnav.intent.IntentResolver import io.element.android.appnav.intent.ResolvedIntent @@ -66,6 +65,7 @@ import io.element.android.libraries.ui.common.nodes.emptyNode import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.watchers.AnalyticsColdStartWatcher +import io.element.android.services.appnavstate.api.ROOM_OPENED_FROM_NOTIFICATION import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -318,7 +318,8 @@ class RootFlowNode( val resolvedIntent = intentResolver.resolve(intent) ?: return when (resolvedIntent) { is ResolvedIntent.Navigation -> { - if (intent.getBooleanExtra("from_notification", false) && resolvedIntent.deeplinkData is DeeplinkData.Room) { + val openingRoomFromNotification = intent.getBooleanExtra(ROOM_OPENED_FROM_NOTIFICATION, false) + if (openingRoomFromNotification && resolvedIntent.deeplinkData is DeeplinkData.Room) { analyticsService.startLongRunningTransaction(AnalyticsLongRunningTransaction.NotificationTapOpensTimeline) } navigateTo(resolvedIntent.deeplinkData) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt index b4b0d92888..82ee730d17 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.push.impl.intent import android.content.Intent +import android.os.Bundle import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId @@ -23,5 +24,6 @@ interface IntentProvider { roomId: RoomId?, threadId: ThreadId?, eventId: EventId?, + extras: Bundle? = null, ): Intent } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt index ff969c2b84..9533f6b0ac 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt @@ -15,6 +15,7 @@ import androidx.annotation.ColorInt import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.MessagingStyle import androidx.core.app.Person +import androidx.core.os.bundleOf import coil3.ImageLoader import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding @@ -44,6 +45,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableMess import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.services.appnavstate.api.ROOM_OPENED_FROM_NOTIFICATION import io.element.android.services.toolbox.api.strings.StringProvider interface NotificationCreator { @@ -138,7 +140,12 @@ class DefaultNotificationCreator( val eventId = events.firstOrNull()?.eventId val openIntent = when { threadId != null -> pendingIntentFactory.createOpenThreadPendingIntent(roomInfo.sessionId, roomInfo.roomId, eventId, threadId) - else -> pendingIntentFactory.createOpenRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId, eventId) + else -> pendingIntentFactory.createOpenRoomPendingIntent( + sessionId = roomInfo.sessionId, + roomId = roomInfo.roomId, + eventId = eventId, + extras = bundleOf(ROOM_OPENED_FROM_NOTIFICATION to true), + ) } val containsMissedCall = events.any { it.type == EventType.RTC_NOTIFICATION } val channelId = if (containsMissedCall) { @@ -233,7 +240,11 @@ class DefaultNotificationCreator( .addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent)) .addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent)) // Build the pending intent for when the notification is clicked - .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent(inviteNotifiableEvent.sessionId, inviteNotifiableEvent.roomId, null)) + .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent( + sessionId = inviteNotifiableEvent.sessionId, + roomId = inviteNotifiableEvent.roomId, + eventId = null, + )) .apply { if (inviteNotifiableEvent.noisy) { // Compat @@ -265,7 +276,12 @@ class DefaultNotificationCreator( .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .configureWith(notificationAccountParams) .setAutoCancel(true) - .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent(simpleNotifiableEvent.sessionId, simpleNotifiableEvent.roomId, null)) + .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent( + sessionId = simpleNotifiableEvent.sessionId, + roomId = simpleNotifiableEvent.roomId, + eventId = null, + extras = bundleOf(ROOM_OPENED_FROM_NOTIFICATION to true), + )) .apply { if (simpleNotifiableEvent.noisy) { // Compat diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt index e68efebb14..07fa70a6c8 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt @@ -11,6 +11,7 @@ package io.element.android.libraries.push.impl.notifications.factories import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.os.Bundle import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.uri.createIgnoredUri import io.element.android.libraries.di.annotations.ApplicationContext @@ -31,20 +32,20 @@ class PendingIntentFactory( private val clock: SystemClock, private val actionIds: NotificationActionIds, ) { - fun createOpenSessionPendingIntent(sessionId: SessionId): PendingIntent? { - return createRoomPendingIntent(sessionId = sessionId, roomId = null, eventId = null, threadId = null) + fun createOpenSessionPendingIntent(sessionId: SessionId, extras: Bundle? = null): PendingIntent? { + return createRoomPendingIntent(sessionId = sessionId, roomId = null, eventId = null, threadId = null, extras = extras) } - fun createOpenRoomPendingIntent(sessionId: SessionId, roomId: RoomId, eventId: EventId?): PendingIntent? { - return createRoomPendingIntent(sessionId = sessionId, roomId = roomId, eventId = eventId, threadId = null) + fun createOpenRoomPendingIntent(sessionId: SessionId, roomId: RoomId, eventId: EventId?, extras: Bundle? = null): PendingIntent? { + return createRoomPendingIntent(sessionId = sessionId, roomId = roomId, eventId = eventId, threadId = null, extras = extras) } - fun createOpenThreadPendingIntent(sessionId: SessionId, roomId: RoomId, eventId: EventId?, threadId: ThreadId): PendingIntent? { - return createRoomPendingIntent(sessionId = sessionId, roomId = roomId, eventId = eventId, threadId = threadId) + fun createOpenThreadPendingIntent(sessionId: SessionId, roomId: RoomId, eventId: EventId?, threadId: ThreadId, extras: Bundle? = null): PendingIntent? { + return createRoomPendingIntent(sessionId = sessionId, roomId = roomId, eventId = eventId, threadId = threadId, extras = extras) } - private fun createRoomPendingIntent(sessionId: SessionId, roomId: RoomId?, eventId: EventId?, threadId: ThreadId?): PendingIntent? { - val intent = intentProvider.getViewRoomIntent(sessionId = sessionId, roomId = roomId, eventId = eventId, threadId = threadId) + private fun createRoomPendingIntent(sessionId: SessionId, roomId: RoomId?, eventId: EventId?, threadId: ThreadId?, extras: Bundle? = null): PendingIntent? { + val intent = intentProvider.getViewRoomIntent(sessionId = sessionId, roomId = roomId, eventId = eventId, threadId = threadId, extras = extras) return PendingIntent.getActivity( context, clock.epochMillis().toInt(), diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/FakeIntentProvider.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/FakeIntentProvider.kt index fdbe927190..62ab850ae7 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/FakeIntentProvider.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/FakeIntentProvider.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.push.impl.notifications.factories import android.content.Intent +import android.os.Bundle import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId @@ -16,5 +17,11 @@ import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.impl.intent.IntentProvider class FakeIntentProvider : IntentProvider { - override fun getViewRoomIntent(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?, eventId: EventId?) = Intent(Intent.ACTION_VIEW) + override fun getViewRoomIntent( + sessionId: SessionId, + roomId: RoomId?, + threadId: ThreadId?, + eventId: EventId?, + extras: Bundle?, + ) = Intent(Intent.ACTION_VIEW) } diff --git a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/IntentNavigationExtras.kt b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/IntentNavigationExtras.kt new file mode 100644 index 0000000000..4851503984 --- /dev/null +++ b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/IntentNavigationExtras.kt @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.appnavstate.api + +const val ROOM_OPENED_FROM_NOTIFICATION = "opened_from_notification"