From 75887003908fcc268831aa83fd13d2992f8e4b22 Mon Sep 17 00:00:00 2001 From: Shahroz Khan Date: Fri, 29 Sep 2023 13:20:38 +0500 Subject: [PATCH 01/21] ci: fix the min version for semantic release (#266) --- .github/workflows/deploy-sdk.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/deploy-sdk.yml b/.github/workflows/deploy-sdk.yml index 7410d8353..06e901e68 100644 --- a/.github/workflows/deploy-sdk.yml +++ b/.github/workflows/deploy-sdk.yml @@ -37,11 +37,9 @@ jobs: id: semantic-release with: dry_run: false - # version numbers below can be in many forms: M, M.m, M.m.p + semantic_version: latest extra_plugins: | conventional-changelog-conventionalcommits - @semantic-release/changelog - @semantic-release/git @semantic-release/github @semantic-release/exec env: From 6f7dc993322ed772750f7e417a013e4bb2379089 Mon Sep 17 00:00:00 2001 From: Rehan Date: Mon, 2 Oct 2023 13:52:06 +0500 Subject: [PATCH 02/21] updated ModuleMessagingConfigTest --- .../io/customer/messagingpush/ModuleMessagingConfigTest.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/ModuleMessagingConfigTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/ModuleMessagingConfigTest.kt index 52f279675..7e1d58de4 100644 --- a/messagingpush/src/sharedTest/java/io/customer/messagingpush/ModuleMessagingConfigTest.kt +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/ModuleMessagingConfigTest.kt @@ -2,12 +2,14 @@ package io.customer.messagingpush import androidx.test.ext.junit.runners.AndroidJUnit4 import io.customer.commontest.BaseTest +import io.customer.messagingpush.config.PushClickBehavior import io.customer.messagingpush.data.communication.CustomerIOPushNotificationCallback import io.customer.messagingpush.di.moduleConfig import io.customer.sdk.CustomerIOConfig import io.customer.sdk.CustomerIOInstance import io.customer.sdk.device.DeviceTokenProvider import io.customer.sdk.module.CustomerIOModule +import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeNull import org.amshove.kluent.shouldBeTrue @@ -53,6 +55,7 @@ internal class ModuleMessagingConfigTest : BaseTest() { moduleConfig.notificationCallback.shouldBeNull() moduleConfig.redirectDeepLinksToOtherApps.shouldBeTrue() + moduleConfig.pushClickBehavior shouldBeEqualTo PushClickBehavior.ACTIVITY_NO_FLAGS } @Test @@ -69,6 +72,7 @@ internal class ModuleMessagingConfigTest : BaseTest() { moduleConfig.autoTrackPushEvents.shouldBeTrue() moduleConfig.notificationCallback.shouldBeNull() moduleConfig.redirectDeepLinksToOtherApps.shouldBeTrue() + moduleConfig.pushClickBehavior shouldBeEqualTo PushClickBehavior.ACTIVITY_NO_FLAGS } @Test @@ -78,6 +82,7 @@ internal class ModuleMessagingConfigTest : BaseTest() { setAutoTrackPushEvents(false) setNotificationCallback(object : CustomerIOPushNotificationCallback {}) setRedirectDeepLinksToOtherApps(false) + setPushClickBehavior(PushClickBehavior.RESET_TASK_STACK) }.build(), overrideCustomerIO = customerIOMock, overrideDiGraph = di @@ -89,5 +94,6 @@ internal class ModuleMessagingConfigTest : BaseTest() { moduleConfig.autoTrackPushEvents.shouldBeFalse() moduleConfig.notificationCallback.shouldNotBeNull() moduleConfig.redirectDeepLinksToOtherApps.shouldBeFalse() + moduleConfig.pushClickBehavior shouldBeEqualTo PushClickBehavior.RESET_TASK_STACK } } From d540ba65fc07fca15aa635ec5c229ff9ed4c3a0e Mon Sep 17 00:00:00 2001 From: Rehan Date: Mon, 2 Oct 2023 14:07:30 +0500 Subject: [PATCH 03/21] added CustomerIOPushNotificationHandlerTest --- .../CustomerIOPushNotificationHandler.kt | 4 +- .../CustomerIOPushNotificationHandlerTest.kt | 85 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt diff --git a/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOPushNotificationHandler.kt b/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOPushNotificationHandler.kt index 031bcc9f2..1d2d315d5 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOPushNotificationHandler.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/CustomerIOPushNotificationHandler.kt @@ -12,6 +12,7 @@ import android.os.Build import android.os.Bundle import androidx.annotation.ColorInt import androidx.annotation.DrawableRes +import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationCompat import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage @@ -217,7 +218,8 @@ internal class CustomerIOPushNotificationHandler( notificationManager.notify(requestCode, notification) } - private fun createIntentForNotificationClick( + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun createIntentForNotificationClick( context: Context, requestCode: Int, payload: CustomerIOParsedPushPayload diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt new file mode 100644 index 000000000..ed8db9df7 --- /dev/null +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt @@ -0,0 +1,85 @@ +package io.customer.messagingpush + +import android.app.PendingIntent +import android.os.Bundle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.firebase.messaging.RemoteMessage +import io.customer.commontest.BaseTest +import io.customer.messagingpush.activity.NotificationClickReceiverActivity +import io.customer.messagingpush.data.model.CustomerIOParsedPushPayload +import io.customer.messagingpush.extensions.parcelable +import io.customer.sdk.extensions.random +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.robolectric.Shadows +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +internal class CustomerIOPushNotificationHandlerTest : BaseTest() { + + private lateinit var pushNotificationHandler: CustomerIOPushNotificationHandler + private lateinit var pushNotificationPayload: CustomerIOParsedPushPayload + + @Before + override fun setup() { + super.setup() + + val extras = Bundle.EMPTY + pushNotificationHandler = CustomerIOPushNotificationHandler(mock(), RemoteMessage(extras)) + pushNotificationPayload = CustomerIOParsedPushPayload( + extras = extras, + deepLink = String.random, + cioDeliveryId = String.random, + cioDeliveryToken = String.random, + title = String.random, + body = String.random + ) + } + + @Test + @Config(sdk = [android.os.Build.VERSION_CODES.LOLLIPOP]) + fun createIntentForNotificationClick_preAndroidM_shouldNotSetImmutableFlag() { + val actualPendingIntent = pushNotificationHandler.createIntentForNotificationClick( + context, + Int.random(1000, 9999), + pushNotificationPayload + ) + + val expectedIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT + Shadows.shadowOf(actualPendingIntent).flags shouldBeEqualTo expectedIntentFlags + } + + @Test + @Config(sdk = [android.os.Build.VERSION_CODES.TIRAMISU]) + fun createIntentForNotificationClick_androidMOrHigher_shouldSetImmutableFlag() { + val actualPendingIntent = pushNotificationHandler.createIntentForNotificationClick( + context, + Int.random(1000, 9999), + pushNotificationPayload + ) + + val expectedIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + Shadows.shadowOf(actualPendingIntent).flags shouldBeEqualTo expectedIntentFlags + } + + @Test + fun createIntentForNotificationClick_validPayload_shouldStartDeepLinkedActivity() { + val actualPendingIntent = pushNotificationHandler.createIntentForNotificationClick( + context, + Int.random(1000, 9999), + pushNotificationPayload + ) + + actualPendingIntent.send() + val nextStartedActivity = Shadows.shadowOf(application).nextStartedActivity + val nextStartedActivityIntent = Shadows.shadowOf(nextStartedActivity) + val nextStartedActivityPayload: CustomerIOParsedPushPayload? = + nextStartedActivity.extras?.parcelable(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA) + + nextStartedActivityIntent.intentClass shouldBeEqualTo NotificationClickReceiverActivity::class.java + nextStartedActivityPayload shouldBeEqualTo pushNotificationPayload + } +} From 0061baa7e639babb3e7db5f89a82b8162f7fe279 Mon Sep 17 00:00:00 2001 From: Rehan Date: Mon, 2 Oct 2023 14:42:47 +0500 Subject: [PATCH 04/21] moved processNotificationClick to PushMessageProcessor --- .../NotificationClickReceiverActivity.kt | 108 ++---------------- .../messagingpush/di/DiGraphMessagingPush.kt | 1 + .../processor/PushMessageProcessor.kt | 2 + .../processor/PushMessageProcessorImpl.kt | 96 +++++++++++++++- 4 files changed, 110 insertions(+), 97 deletions(-) diff --git a/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt b/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt index 8fb42e27a..43dc0f31d 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt @@ -3,18 +3,9 @@ package io.customer.messagingpush.activity import android.app.Activity import android.content.Intent import android.os.Bundle -import androidx.core.app.TaskStackBuilder -import io.customer.messagingpush.MessagingPushModuleConfig -import io.customer.messagingpush.config.PushClickBehavior -import io.customer.messagingpush.data.model.CustomerIOParsedPushPayload -import io.customer.messagingpush.di.deepLinkUtil -import io.customer.messagingpush.di.moduleConfig -import io.customer.messagingpush.extensions.parcelable -import io.customer.messagingpush.util.DeepLinkUtil +import io.customer.messagingpush.di.pushMessageProcessor import io.customer.sdk.CustomerIO import io.customer.sdk.CustomerIOShared -import io.customer.sdk.data.request.MetricEvent -import io.customer.sdk.extensions.takeIfNotBlank import io.customer.sdk.tracking.TrackableScreen import io.customer.sdk.util.Logger @@ -43,98 +34,23 @@ class NotificationClickReceiverActivity : Activity(), TrackableScreen { } private fun handleIntent(data: Intent?) { - kotlin.runCatching { - val payload: CustomerIOParsedPushPayload? = - data?.extras?.parcelable(NOTIFICATION_PAYLOAD_EXTRA) - if (payload == null) { - logger.error("Payload is null, cannot handle notification intent") + if (data == null) { + // This should never happen ideally + logger.error("Intent is null, cannot process notification click") + } else { + val sdkInstance = CustomerIO.instanceOrNull(context = this) + if (sdkInstance == null) { + logger.error("SDK is not initialized, cannot handle notification intent") } else { - processNotificationIntent(payload = payload) + sdkInstance.diGraph.pushMessageProcessor.processNotificationClick( + activity = this, + intent = data + ) } - }.onFailure { ex -> - logger.error("Failed to process notification intent: ${ex.message}") } finish() } - private fun processNotificationIntent(payload: CustomerIOParsedPushPayload) { - val sdkInstance = CustomerIO.instanceOrNull(context = this) - if (sdkInstance == null) { - logger.error("SDK is not initialized, cannot handle notification intent") - return - } - - val moduleConfig: MessagingPushModuleConfig = sdkInstance.diGraph.moduleConfig - trackMetrics(moduleConfig, payload) - handleDeepLink(moduleConfig, payload) - } - - private fun trackMetrics( - moduleConfig: MessagingPushModuleConfig, - payload: CustomerIOParsedPushPayload - ) { - if (moduleConfig.autoTrackPushEvents) { - CustomerIO.instance().trackMetric( - payload.cioDeliveryId, - MetricEvent.opened, - payload.cioDeliveryToken - ) - } - } - - private fun handleDeepLink( - moduleConfig: MessagingPushModuleConfig, - payload: CustomerIOParsedPushPayload - ) { - val deepLinkUtil: DeepLinkUtil = CustomerIO.instance().diGraph.deepLinkUtil - val deepLink = payload.deepLink?.takeIfNotBlank() - - // check if host app overrides the handling of deeplink - val notificationCallback = moduleConfig.notificationCallback - val taskStackFromPayload = notificationCallback?.createTaskStackFromPayload(this, payload) - if (taskStackFromPayload != null) { - logger.info("Notification target overridden by createTaskStackFromPayload, starting new stack for link $deepLink") - taskStackFromPayload.startActivities() - return - } - - // Get the default intent for the host app - val defaultHostAppIntent = deepLinkUtil.createDefaultHostAppIntent(context = this) - // Check if the deep links are handled within the host app - val deepLinkHostAppIntent = deepLink?.let { link -> - deepLinkUtil.createDeepLinkHostAppIntent(context = this, link = link) - } - // Check if the deep links can be opened outside the host app - val deepLinkExternalIntent = deepLink?.let { link -> - deepLinkUtil.createDeepLinkExternalIntent(context = this, link = link) - } - val deepLinkIntent: Intent = deepLinkHostAppIntent - ?: deepLinkExternalIntent - ?: defaultHostAppIntent - ?: return - deepLinkIntent.putExtra(NOTIFICATION_PAYLOAD_EXTRA, payload) - logger.info("Dispatching notification with link $deepLink to intent: $deepLinkIntent with behavior: ${moduleConfig.pushClickBehavior}") - - when (moduleConfig.pushClickBehavior) { - PushClickBehavior.RESET_TASK_STACK -> { - val taskStackBuilder = TaskStackBuilder.create(this).apply { - addNextIntentWithParentStack(deepLinkIntent) - } - taskStackBuilder.startActivities() - } - - PushClickBehavior.ACTIVITY_PREVENT_RESTART -> { - deepLinkIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_SINGLE_TOP - startActivity(deepLinkIntent) - } - - PushClickBehavior.ACTIVITY_NO_FLAGS -> { - startActivity(deepLinkIntent) - } - } - } - companion object { const val NOTIFICATION_PAYLOAD_EXTRA = "CIO_NotificationPayloadExtras" } diff --git a/messagingpush/src/main/java/io/customer/messagingpush/di/DiGraphMessagingPush.kt b/messagingpush/src/main/java/io/customer/messagingpush/di/DiGraphMessagingPush.kt index a515ec2e8..f26c6e9d9 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/di/DiGraphMessagingPush.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/di/DiGraphMessagingPush.kt @@ -40,6 +40,7 @@ internal val CustomerIOComponent.pushMessageProcessor: PushMessageProcessor PushMessageProcessorImpl( logger = logger, moduleConfig = moduleConfig, + deepLinkUtil = deepLinkUtil, trackRepository = trackRepository ) } diff --git a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt index 2e6c9f16e..39b72f53f 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt @@ -1,5 +1,6 @@ package io.customer.messagingpush.processor +import android.content.Context import android.content.Intent import com.google.firebase.messaging.RemoteMessage import io.customer.base.internal.InternalCustomerIOApi @@ -44,6 +45,7 @@ interface PushMessageProcessor { * @param deliveryToken received in push payload */ fun processRemoteMessageDeliveredMetrics(deliveryId: String, deliveryToken: String) + fun processNotificationClick(activity: Context, intent: Intent) companion object { // Count of messages stored in memory diff --git a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt index c8dee535f..291656e09 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt @@ -1,16 +1,25 @@ package io.customer.messagingpush.processor +import android.content.Context import android.content.Intent import androidx.annotation.VisibleForTesting +import androidx.core.app.TaskStackBuilder import io.customer.messagingpush.MessagingPushModuleConfig +import io.customer.messagingpush.activity.NotificationClickReceiverActivity +import io.customer.messagingpush.config.PushClickBehavior +import io.customer.messagingpush.data.model.CustomerIOParsedPushPayload +import io.customer.messagingpush.extensions.parcelable +import io.customer.messagingpush.util.DeepLinkUtil import io.customer.messagingpush.util.PushTrackingUtil import io.customer.sdk.data.request.MetricEvent +import io.customer.sdk.extensions.takeIfNotBlank import io.customer.sdk.repository.TrackRepository import io.customer.sdk.util.Logger -internal class PushMessageProcessorImpl( +internal open class PushMessageProcessorImpl( private val logger: Logger, private val moduleConfig: MessagingPushModuleConfig, + private val deepLinkUtil: DeepLinkUtil, private val trackRepository: TrackRepository ) : PushMessageProcessor { @@ -85,4 +94,89 @@ internal class PushMessageProcessorImpl( ) } } + + override fun processNotificationClick(activity: Context, intent: Intent) { + kotlin.runCatching { + val payload: CustomerIOParsedPushPayload? = + intent.extras?.parcelable(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA) + if (payload == null) { + logger.error("Payload is null, cannot handle notification intent") + } else { + processNotificationIntent(activity = activity, payload = payload) + } + }.onFailure { ex -> + logger.error("Failed to process notification intent: ${ex.message}") + } + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun processNotificationIntent(activity: Context, payload: CustomerIOParsedPushPayload) { + trackMetrics(payload) + handleDeepLink(activity, payload) + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun trackMetrics(payload: CustomerIOParsedPushPayload) { + if (moduleConfig.autoTrackPushEvents) { + trackRepository.trackMetric( + deliveryID = payload.cioDeliveryId, + event = MetricEvent.opened, + deviceToken = payload.cioDeliveryToken + ) + } + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun handleDeepLink(activity: Context, payload: CustomerIOParsedPushPayload) { + val deepLink = payload.deepLink?.takeIfNotBlank() + + // check if host app overrides the handling of deeplink + val notificationCallback = moduleConfig.notificationCallback + val taskStackFromPayload = + notificationCallback?.createTaskStackFromPayload(activity, payload) + if (taskStackFromPayload != null) { + logger.info("Notification target overridden by createTaskStackFromPayload, starting new stack for link $deepLink") + taskStackFromPayload.startActivities() + return + } + + // Get the default intent for the host app + val defaultHostAppIntent = deepLinkUtil.createDefaultHostAppIntent(context = activity) + // Check if the deep links are handled within the host app + val deepLinkHostAppIntent = deepLink?.let { link -> + deepLinkUtil.createDeepLinkHostAppIntent(context = activity, link = link) + } + // Check if the deep links can be opened outside the host app + val deepLinkExternalIntent = deepLink?.let { link -> + deepLinkUtil.createDeepLinkExternalIntent(context = activity, link = link) + } + val deepLinkIntent: Intent = deepLinkHostAppIntent + ?: deepLinkExternalIntent + ?: defaultHostAppIntent + ?: return + deepLinkIntent.putExtra( + NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, + payload + ) + logger.info("Dispatching notification with link $deepLink to intent: $deepLinkIntent with behavior: ${moduleConfig.pushClickBehavior}") + + when (moduleConfig.pushClickBehavior) { + PushClickBehavior.RESET_TASK_STACK -> { + val taskStackBuilder = TaskStackBuilder.create(activity).apply { + addNextIntentWithParentStack(deepLinkIntent) + } + taskStackBuilder.startActivities() + } + + PushClickBehavior.ACTIVITY_PREVENT_RESTART -> { + deepLinkIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_SINGLE_TOP + activity.startActivity(deepLinkIntent) + } + + PushClickBehavior.ACTIVITY_NO_FLAGS -> { + activity.startActivity(deepLinkIntent) + } + } + } } From e0c279eda222401a97d937c05c77ab8065ed5134 Mon Sep 17 00:00:00 2001 From: Rehan Date: Mon, 2 Oct 2023 16:22:58 +0500 Subject: [PATCH 05/21] added docs --- .../NotificationClickReceiverActivity.kt | 2 +- .../processor/PushMessageProcessor.kt | 17 ++++++++++- .../processor/PushMessageProcessorImpl.kt | 29 ++++++++++--------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt b/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt index 43dc0f31d..0d4d5caaf 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt @@ -43,7 +43,7 @@ class NotificationClickReceiverActivity : Activity(), TrackableScreen { logger.error("SDK is not initialized, cannot handle notification intent") } else { sdkInstance.diGraph.pushMessageProcessor.processNotificationClick( - activity = this, + activityContext = this, intent = data ) } diff --git a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt index 39b72f53f..18b1eaf61 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt @@ -45,7 +45,22 @@ interface PushMessageProcessor { * @param deliveryToken received in push payload */ fun processRemoteMessageDeliveredMetrics(deliveryId: String, deliveryToken: String) - fun processNotificationClick(activity: Context, intent: Intent) + + /** + * Executes the necessary actions when a notification is clicked by the user. + * + * This method performs the following tasks: + * 1. Tracks 'opened' metrics for the notification. + * 2. Resolves the deep link, if available in the notification payload, and navigates to the corresponding screen. + * 3. If no deep link is provided, opens the default launcher screen. + * + * This method may only be called from `onCreate` or `onNewIntent` methods of notification handler activity. + * + * @param activityContext context should be an activity context and not application context as + * this will be used to start desired activity + * @param intent intent received by the activity + */ + fun processNotificationClick(activityContext: Context, intent: Intent) companion object { // Count of messages stored in memory diff --git a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt index 291656e09..e160e1f02 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt @@ -95,14 +95,14 @@ internal open class PushMessageProcessorImpl( } } - override fun processNotificationClick(activity: Context, intent: Intent) { + override fun processNotificationClick(activityContext: Context, intent: Intent) { kotlin.runCatching { val payload: CustomerIOParsedPushPayload? = intent.extras?.parcelable(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA) if (payload == null) { logger.error("Payload is null, cannot handle notification intent") } else { - processNotificationIntent(activity = activity, payload = payload) + processNotificationIntent(activityContext, payload) } }.onFailure { ex -> logger.error("Failed to process notification intent: ${ex.message}") @@ -110,9 +110,9 @@ internal open class PushMessageProcessorImpl( } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun processNotificationIntent(activity: Context, payload: CustomerIOParsedPushPayload) { + fun processNotificationIntent(activityContext: Context, payload: CustomerIOParsedPushPayload) { trackMetrics(payload) - handleDeepLink(activity, payload) + handleDeepLink(activityContext, payload) } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @@ -127,13 +127,15 @@ internal open class PushMessageProcessorImpl( } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun handleDeepLink(activity: Context, payload: CustomerIOParsedPushPayload) { + fun handleDeepLink(activityContext: Context, payload: CustomerIOParsedPushPayload) { val deepLink = payload.deepLink?.takeIfNotBlank() // check if host app overrides the handling of deeplink val notificationCallback = moduleConfig.notificationCallback - val taskStackFromPayload = - notificationCallback?.createTaskStackFromPayload(activity, payload) + val taskStackFromPayload = notificationCallback?.createTaskStackFromPayload( + context = activityContext, + payload = payload + ) if (taskStackFromPayload != null) { logger.info("Notification target overridden by createTaskStackFromPayload, starting new stack for link $deepLink") taskStackFromPayload.startActivities() @@ -141,14 +143,15 @@ internal open class PushMessageProcessorImpl( } // Get the default intent for the host app - val defaultHostAppIntent = deepLinkUtil.createDefaultHostAppIntent(context = activity) + val defaultHostAppIntent = + deepLinkUtil.createDefaultHostAppIntent(context = activityContext) // Check if the deep links are handled within the host app val deepLinkHostAppIntent = deepLink?.let { link -> - deepLinkUtil.createDeepLinkHostAppIntent(context = activity, link = link) + deepLinkUtil.createDeepLinkHostAppIntent(context = activityContext, link = link) } // Check if the deep links can be opened outside the host app val deepLinkExternalIntent = deepLink?.let { link -> - deepLinkUtil.createDeepLinkExternalIntent(context = activity, link = link) + deepLinkUtil.createDeepLinkExternalIntent(context = activityContext, link = link) } val deepLinkIntent: Intent = deepLinkHostAppIntent ?: deepLinkExternalIntent @@ -162,7 +165,7 @@ internal open class PushMessageProcessorImpl( when (moduleConfig.pushClickBehavior) { PushClickBehavior.RESET_TASK_STACK -> { - val taskStackBuilder = TaskStackBuilder.create(activity).apply { + val taskStackBuilder = TaskStackBuilder.create(activityContext).apply { addNextIntentWithParentStack(deepLinkIntent) } taskStackBuilder.startActivities() @@ -171,11 +174,11 @@ internal open class PushMessageProcessorImpl( PushClickBehavior.ACTIVITY_PREVENT_RESTART -> { deepLinkIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP - activity.startActivity(deepLinkIntent) + activityContext.startActivity(deepLinkIntent) } PushClickBehavior.ACTIVITY_NO_FLAGS -> { - activity.startActivity(deepLinkIntent) + activityContext.startActivity(deepLinkIntent) } } } From b4e9b6e25f8dfb46236ae4f16718de209bbfebd4 Mon Sep 17 00:00:00 2001 From: Rehan Date: Mon, 2 Oct 2023 17:28:49 +0500 Subject: [PATCH 06/21] added some tests --- .../processor/PushMessageProcessorTest.kt | 258 +++++++++++++++++- 1 file changed, 256 insertions(+), 2 deletions(-) diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt index 893844f2c..814d8391f 100644 --- a/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt @@ -1,12 +1,19 @@ package io.customer.messagingpush.processor import android.content.Intent +import android.net.Uri import android.os.Bundle +import androidx.core.app.TaskStackBuilder import androidx.test.ext.junit.runners.AndroidJUnit4 import io.customer.commontest.BaseTest import io.customer.messagingpush.MessagingPushModuleConfig import io.customer.messagingpush.ModuleMessagingPushFCM -import io.customer.messagingpush.di.moduleConfig +import io.customer.messagingpush.activity.NotificationClickReceiverActivity +import io.customer.messagingpush.config.PushClickBehavior +import io.customer.messagingpush.data.communication.CustomerIOPushNotificationCallback +import io.customer.messagingpush.data.model.CustomerIOParsedPushPayload +import io.customer.messagingpush.di.pushMessageProcessor +import io.customer.messagingpush.util.DeepLinkUtil import io.customer.messagingpush.util.PushTrackingUtil import io.customer.sdk.CustomerIOConfig import io.customer.sdk.CustomerIOInstance @@ -14,27 +21,70 @@ import io.customer.sdk.data.request.MetricEvent import io.customer.sdk.extensions.random import io.customer.sdk.module.CustomerIOModule import io.customer.sdk.repository.TrackRepository +import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue +import org.amshove.kluent.shouldNotBe +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever +import org.robolectric.Shadows @RunWith(AndroidJUnit4::class) class PushMessageProcessorTest : BaseTest() { private val modules = hashMapOf>() private val customerIOMock: CustomerIOInstance = mock() + private val deepLinkUtilMock: DeepLinkUtil = mock() private val trackRepositoryMock: TrackRepository = mock() override fun setupConfig(): CustomerIOConfig = createConfig( modules = modules ) + @Before + override fun setup() { + super.setup() + + di.overrideDependency(DeepLinkUtil::class.java, deepLinkUtilMock) + di.overrideDependency(TrackRepository::class.java, trackRepositoryMock) + } + private fun pushMessageProcessor(): PushMessageProcessorImpl { - return PushMessageProcessorImpl(di.logger, di.moduleConfig, trackRepositoryMock) + return di.pushMessageProcessor as PushMessageProcessorImpl + } + + private fun pushMessagePayload(deepLink: String? = null): CustomerIOParsedPushPayload { + return CustomerIOParsedPushPayload( + extras = Bundle.EMPTY, + deepLink = deepLink, + cioDeliveryId = String.random, + cioDeliveryToken = String.random, + title = String.random, + body = String.random + ) + } + + private fun setupModuleConfig( + pushClickBehavior: PushClickBehavior? = null, + autoTrackPushEvents: Boolean? = null, + notificationCallback: CustomerIOPushNotificationCallback? = null + ) { + modules[ModuleMessagingPushFCM.MODULE_NAME] = ModuleMessagingPushFCM( + overrideCustomerIO = customerIOMock, + overrideDiGraph = di, + moduleConfig = with(MessagingPushModuleConfig.Builder()) { + autoTrackPushEvents?.let { setAutoTrackPushEvents(it) } + notificationCallback?.let { setNotificationCallback(it) } + pushClickBehavior?.let { setPushClickBehavior(it) } + build() + } + ) } @Test @@ -194,4 +244,208 @@ class PushMessageProcessorTest : BaseTest() { givenDeviceToken ) } + + @Test + fun processNotificationClick_givenValidIntent_expectSuccessfulProcessing() { + setupModuleConfig(autoTrackPushEvents = true) + val processor = pushMessageProcessor() + val givenPayload = pushMessagePayload(deepLink = "https://cio.example.com/") + val intent = Intent().apply { + putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) + } + + processor.processNotificationClick(context, intent) + + verify(trackRepositoryMock).trackMetric( + givenPayload.cioDeliveryId, + MetricEvent.opened, + givenPayload.cioDeliveryToken + ) + verify(deepLinkUtilMock).createDefaultHostAppIntent(context) + verify(deepLinkUtilMock).createDeepLinkHostAppIntent(context, givenPayload.deepLink) + verify(deepLinkUtilMock).createDeepLinkExternalIntent(context, givenPayload.deepLink!!) + } + + @Test + fun processNotificationClick_givenAutoTrackingDisabled_expectDoNotTrackOpened() { + setupModuleConfig(autoTrackPushEvents = false) + val processor = pushMessageProcessor() + val givenPayload = pushMessagePayload() + val intent = Intent().apply { + putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) + } + + processor.processNotificationClick(context, intent) + + verifyNoInteractions(trackRepositoryMock) + } + + @Test + fun processNotificationClick_givenNoDeepLink_expectOpenLauncherIntent() { + val processor = pushMessageProcessor() + val givenPayload = pushMessagePayload() + val intent = Intent().apply { + putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) + } + + processor.processNotificationClick(context, intent) + + verify(deepLinkUtilMock).createDefaultHostAppIntent(any()) + verify(deepLinkUtilMock, never()).createDeepLinkHostAppIntent(any(), any()) + verify(deepLinkUtilMock, never()).createDeepLinkExternalIntent(any(), any()) + } + + @Test + fun processNotificationClick_givenCallbackWithDeepLink_expectOpenCallbackIntent() { + val notificationCallback: CustomerIOPushNotificationCallback = mock() + whenever(notificationCallback.createTaskStackFromPayload(any(), any())).thenReturn( + TaskStackBuilder.create(context) + ) + val givenPayload = pushMessagePayload(deepLink = "https://cio.example.com/") + + // Make sure that the callback as expected for all behaviors + for (pushClickBehavior in PushClickBehavior.values()) { + setupModuleConfig( + notificationCallback = notificationCallback, + pushClickBehavior = pushClickBehavior + ) + val processor = pushMessageProcessor() + val intent = Intent().apply { + putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) + } + + processor.processNotificationClick(context, intent) + + verifyNoInteractions(deepLinkUtilMock) + } + } + + @Test + fun processNotificationClick_givenCallbackWithoutDeepLink_expectOpenCallbackIntent() { + val notificationCallback: CustomerIOPushNotificationCallback = mock() + whenever(notificationCallback.createTaskStackFromPayload(any(), any())).thenReturn( + TaskStackBuilder.create(context) + ) + val givenPayload = pushMessagePayload() + + // Make sure that the callback as expected for all behaviors + for (pushClickBehavior in PushClickBehavior.values()) { + setupModuleConfig( + notificationCallback = notificationCallback, + pushClickBehavior = pushClickBehavior + ) + val processor = pushMessageProcessor() + val intent = Intent().apply { + putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) + } + + processor.processNotificationClick(context, intent) + + verifyNoInteractions(deepLinkUtilMock) + } + } + + @Test + fun processNotificationClick_givenPushBehavior_expectResetTaskStack() { + setupModuleConfig( + autoTrackPushEvents = false, + pushClickBehavior = PushClickBehavior.RESET_TASK_STACK + ) + val givenPackageName = "io.customer.example" + val givenDeepLink = "https://cio.example.com/" + whenever(deepLinkUtilMock.createDeepLinkHostAppIntent(any(), any())).thenReturn( + Intent(Intent.ACTION_VIEW, Uri.parse(givenDeepLink)).apply { + setPackage(givenPackageName) + } + ) + val givenPayload = pushMessagePayload(deepLink = givenDeepLink) + val processor = pushMessageProcessor() + val intent = Intent().apply { + putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) + } + + processor.processNotificationClick(context, intent) + + // The intent will be started with the default flags based on the activity launch mode + // Also, we cannot verify the back stack as it is not exposed by the testing framework + val expectedIntentFlags = + Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_TASK_ON_HOME + val nextStartedActivity = Shadows.shadowOf(application).nextStartedActivity + nextStartedActivity shouldNotBe null + nextStartedActivity.action shouldBeEqualTo Intent.ACTION_VIEW + nextStartedActivity.dataString shouldBeEqualTo givenDeepLink + nextStartedActivity.flags shouldBeEqualTo expectedIntentFlags + nextStartedActivity.`package` shouldBeEqualTo givenPackageName + } + + @Test + fun processNotificationClick_givenPushBehavior_expectPreventRestart() { + setupModuleConfig( + autoTrackPushEvents = false, + pushClickBehavior = PushClickBehavior.ACTIVITY_PREVENT_RESTART + ) + val givenPackageName = "io.customer.example" + val givenDeepLink = "https://cio.example.com/" + whenever(deepLinkUtilMock.createDeepLinkHostAppIntent(any(), any())).thenReturn( + Intent(Intent.ACTION_VIEW, Uri.parse(givenDeepLink)).apply { + setPackage(givenPackageName) + } + ) + val givenPayload = pushMessagePayload(deepLink = givenDeepLink) + val processor = pushMessageProcessor() + val intent = Intent().apply { + putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) + } + + processor.processNotificationClick(context, intent) + + val expectedIntentFlags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + val nextStartedActivity = Shadows.shadowOf(application).nextStartedActivity + nextStartedActivity shouldNotBe null + nextStartedActivity.action shouldBeEqualTo Intent.ACTION_VIEW + nextStartedActivity.dataString shouldBeEqualTo givenDeepLink + nextStartedActivity.flags shouldBeEqualTo expectedIntentFlags + nextStartedActivity.`package` shouldBeEqualTo givenPackageName + } + + @Test + fun processNotificationClick_givenPushBehavior_expectNoFlags() { + setupModuleConfig( + autoTrackPushEvents = false, + pushClickBehavior = PushClickBehavior.ACTIVITY_NO_FLAGS + ) + val givenPackageName = "io.customer.example" + val givenDeepLink = "https://cio.example.com/" + whenever(deepLinkUtilMock.createDeepLinkHostAppIntent(any(), any())).thenReturn( + Intent(Intent.ACTION_VIEW, Uri.parse(givenDeepLink)).apply { + setPackage(givenPackageName) + } + ) + val givenPayload = pushMessagePayload(deepLink = givenDeepLink) + val processor = pushMessageProcessor() + val intent = Intent().apply { + putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) + } + + processor.processNotificationClick(context, intent) + + // The intent will be started with the default flags based on the activity launch mode + val nextStartedActivity = Shadows.shadowOf(application).nextStartedActivity + nextStartedActivity shouldNotBe null + nextStartedActivity.action shouldBeEqualTo Intent.ACTION_VIEW + nextStartedActivity.dataString shouldBeEqualTo givenDeepLink + nextStartedActivity.`package` shouldBeEqualTo givenPackageName + } + + @Test + fun processNotificationClick_givenEmptyIntent_expectNoProcessing() { + setupModuleConfig(autoTrackPushEvents = true) + val processor = pushMessageProcessor() + val intent = Intent() + + processor.processNotificationClick(context, intent) + + verifyNoInteractions(trackRepositoryMock) + verifyNoInteractions(deepLinkUtilMock) + } } From b22dd7e1494410e38370ecf6d36bac31196ab1c4 Mon Sep 17 00:00:00 2001 From: Rehan Date: Mon, 2 Oct 2023 17:31:45 +0500 Subject: [PATCH 07/21] reverted open modifier --- .../messagingpush/processor/PushMessageProcessorImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt index e160e1f02..330f06375 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt @@ -16,7 +16,7 @@ import io.customer.sdk.extensions.takeIfNotBlank import io.customer.sdk.repository.TrackRepository import io.customer.sdk.util.Logger -internal open class PushMessageProcessorImpl( +internal class PushMessageProcessorImpl( private val logger: Logger, private val moduleConfig: MessagingPushModuleConfig, private val deepLinkUtil: DeepLinkUtil, From 161b9c2c971bde35727659104182ce2d64f44efb Mon Sep 17 00:00:00 2001 From: Rehan Date: Mon, 2 Oct 2023 18:29:45 +0500 Subject: [PATCH 08/21] doc fixes --- .../customer/messagingpush/processor/PushMessageProcessor.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt index 18b1eaf61..759715f8f 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessor.kt @@ -56,8 +56,7 @@ interface PushMessageProcessor { * * This method may only be called from `onCreate` or `onNewIntent` methods of notification handler activity. * - * @param activityContext context should be an activity context and not application context as - * this will be used to start desired activity + * @param activityContext context should be from activity as this will be used for launching activity * @param intent intent received by the activity */ fun processNotificationClick(activityContext: Context, intent: Intent) From ac17ffcc86856d5295428a832eaf6af5dd5fa8ea Mon Sep 17 00:00:00 2001 From: Rehan Date: Mon, 2 Oct 2023 18:31:57 +0500 Subject: [PATCH 09/21] removed needless tests --- .../CustomerIOPushNotificationHandlerTest.kt | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt index ed8db9df7..cb71aa055 100644 --- a/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt @@ -1,6 +1,5 @@ package io.customer.messagingpush -import android.app.PendingIntent import android.os.Bundle import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.firebase.messaging.RemoteMessage @@ -15,7 +14,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.robolectric.Shadows -import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) internal class CustomerIOPushNotificationHandlerTest : BaseTest() { @@ -39,32 +37,6 @@ internal class CustomerIOPushNotificationHandlerTest : BaseTest() { ) } - @Test - @Config(sdk = [android.os.Build.VERSION_CODES.LOLLIPOP]) - fun createIntentForNotificationClick_preAndroidM_shouldNotSetImmutableFlag() { - val actualPendingIntent = pushNotificationHandler.createIntentForNotificationClick( - context, - Int.random(1000, 9999), - pushNotificationPayload - ) - - val expectedIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT - Shadows.shadowOf(actualPendingIntent).flags shouldBeEqualTo expectedIntentFlags - } - - @Test - @Config(sdk = [android.os.Build.VERSION_CODES.TIRAMISU]) - fun createIntentForNotificationClick_androidMOrHigher_shouldSetImmutableFlag() { - val actualPendingIntent = pushNotificationHandler.createIntentForNotificationClick( - context, - Int.random(1000, 9999), - pushNotificationPayload - ) - - val expectedIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - Shadows.shadowOf(actualPendingIntent).flags shouldBeEqualTo expectedIntentFlags - } - @Test fun createIntentForNotificationClick_validPayload_shouldStartDeepLinkedActivity() { val actualPendingIntent = pushNotificationHandler.createIntentForNotificationClick( From 153bc27f28cc0424f86edc74d56204dd06610b40 Mon Sep 17 00:00:00 2001 From: Rehan Date: Mon, 2 Oct 2023 18:34:32 +0500 Subject: [PATCH 10/21] ignored failing test --- .../messagingpush/processor/PushMessageProcessorTest.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt index 814d8391f..75b1bf7d6 100644 --- a/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt @@ -26,6 +26,7 @@ import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue import org.amshove.kluent.shouldNotBe import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any @@ -408,6 +409,10 @@ class PushMessageProcessorTest : BaseTest() { nextStartedActivity.`package` shouldBeEqualTo givenPackageName } + // Ignored as the testing framework does not support verifying the flags + // We'll have to rely on manual testing for this for now + // In future, we can use more advanced testing frameworks to verify this + @Ignore @Test fun processNotificationClick_givenPushBehavior_expectNoFlags() { setupModuleConfig( From 566090bdad0704a6507b31c18c9048e018640e2a Mon Sep 17 00:00:00 2001 From: Rehan Date: Wed, 4 Oct 2023 15:00:50 +0500 Subject: [PATCH 11/21] renamed methods --- .../processor/PushMessageProcessorImpl.kt | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt index 330f06375..f4f3dafd3 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt @@ -102,21 +102,22 @@ internal class PushMessageProcessorImpl( if (payload == null) { logger.error("Payload is null, cannot handle notification intent") } else { - processNotificationIntent(activityContext, payload) + handleNotificationClickIntent(activityContext, payload) } }.onFailure { ex -> logger.error("Failed to process notification intent: ${ex.message}") } } - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun processNotificationIntent(activityContext: Context, payload: CustomerIOParsedPushPayload) { - trackMetrics(payload) - handleDeepLink(activityContext, payload) + private fun handleNotificationClickIntent( + activityContext: Context, + payload: CustomerIOParsedPushPayload + ) { + trackNotificationClickMetrics(payload) + handleNotificationDeepLink(activityContext, payload) } - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun trackMetrics(payload: CustomerIOParsedPushPayload) { + private fun trackNotificationClickMetrics(payload: CustomerIOParsedPushPayload) { if (moduleConfig.autoTrackPushEvents) { trackRepository.trackMetric( deliveryID = payload.cioDeliveryId, @@ -126,8 +127,10 @@ internal class PushMessageProcessorImpl( } } - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - fun handleDeepLink(activityContext: Context, payload: CustomerIOParsedPushPayload) { + private fun handleNotificationDeepLink( + activityContext: Context, + payload: CustomerIOParsedPushPayload + ) { val deepLink = payload.deepLink?.takeIfNotBlank() // check if host app overrides the handling of deeplink From 4db07219dcaeabb2cf5f3071c4e529479e0380c4 Mon Sep 17 00:00:00 2001 From: Rehan Date: Wed, 4 Oct 2023 15:14:31 +0500 Subject: [PATCH 12/21] updated test name --- .../messagingpush/CustomerIOPushNotificationHandlerTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt index cb71aa055..3523ad575 100644 --- a/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/CustomerIOPushNotificationHandlerTest.kt @@ -38,7 +38,7 @@ internal class CustomerIOPushNotificationHandlerTest : BaseTest() { } @Test - fun createIntentForNotificationClick_validPayload_shouldStartDeepLinkedActivity() { + fun createIntentForNotificationClick_givenAnyPayload_shouldStartNotificationClickReceiverActivity() { val actualPendingIntent = pushNotificationHandler.createIntentForNotificationClick( context, Int.random(1000, 9999), From 2b85973aa815c45864a05909a2a5b59515dfc783 Mon Sep 17 00:00:00 2001 From: Rehan Date: Wed, 4 Oct 2023 15:15:45 +0500 Subject: [PATCH 13/21] fixed ignore warning --- .../messagingpush/processor/PushMessageProcessorTest.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt index 75b1bf7d6..77a3751d8 100644 --- a/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt @@ -409,10 +409,11 @@ class PushMessageProcessorTest : BaseTest() { nextStartedActivity.`package` shouldBeEqualTo givenPackageName } - // Ignored as the testing framework does not support verifying the flags - // We'll have to rely on manual testing for this for now - // In future, we can use more advanced testing frameworks to verify this - @Ignore + @Ignore( + "Current testing framework does not support verifying the flags. " + + "We'll have to rely on manual testing for this for now." + + "In future, we can use more advanced testing frameworks to verify this" + ) @Test fun processNotificationClick_givenPushBehavior_expectNoFlags() { setupModuleConfig( From 20ce7de17e92b3a967447d16289cf660b7029bc0 Mon Sep 17 00:00:00 2001 From: Rehan Date: Thu, 5 Oct 2023 20:27:53 +0500 Subject: [PATCH 14/21] added tests for NotificationClickReceiverActivity --- .../NotificationClickReceiverActivityTest.kt | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 messagingpush/src/sharedTest/java/io/customer/messagingpush/activity/NotificationClickReceiverActivityTest.kt diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/activity/NotificationClickReceiverActivityTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/activity/NotificationClickReceiverActivityTest.kt new file mode 100644 index 000000000..d23867fb5 --- /dev/null +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/activity/NotificationClickReceiverActivityTest.kt @@ -0,0 +1,69 @@ +package io.customer.messagingpush.activity + +import android.os.Bundle +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.customer.commontest.BaseIntegrationTest +import io.customer.messagingpush.data.model.CustomerIOParsedPushPayload +import io.customer.messagingpush.processor.PushMessageProcessor +import io.customer.sdk.CustomerIO +import io.customer.sdk.extensions.random +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions + +@RunWith(AndroidJUnit4::class) +class NotificationClickReceiverActivityTest : BaseIntegrationTest() { + private val pushMessageProcessorMock: PushMessageProcessor = mock() + + @Before + override fun setup() { + super.setup() + + di.overrideDependency(PushMessageProcessor::class.java, pushMessageProcessorMock) + } + + private fun pushActivityExtras(): Bundle { + val payload = CustomerIOParsedPushPayload( + extras = Bundle.EMPTY, + deepLink = null, + cioDeliveryId = String.random, + cioDeliveryToken = String.random, + title = String.random, + body = String.random + ) + + return Bundle().apply { + putParcelable(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, payload) + } + } + + @Test + fun clickNotification_givenValidIntent_expectProcessPush() { + val extras = pushActivityExtras() + + val scenario = + ActivityScenario.launch(NotificationClickReceiverActivity::class.java, extras) + + verify(pushMessageProcessorMock).processNotificationClick(any(), any()) + scenario.state shouldBeEqualTo Lifecycle.State.DESTROYED + } + + @Test + fun clickNotification_givenSDKNotInitialized_expectDoNoProcessPush() { + val extras = pushActivityExtras() + + CustomerIO.clearInstance() + val scenario = + ActivityScenario.launch(NotificationClickReceiverActivity::class.java, extras) + + verifyNoInteractions(pushMessageProcessorMock) + scenario.state shouldBeEqualTo Lifecycle.State.DESTROYED + } +} From de0b197917b506f95967c9741833456243b8aa1b Mon Sep 17 00:00:00 2001 From: Rehan Date: Fri, 6 Oct 2023 07:35:50 +0500 Subject: [PATCH 15/21] added more tests --- .../NotificationClickReceiverActivity.kt | 2 +- .../NotificationClickReceiverActivityTest.kt | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt b/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt index 0d4d5caaf..89ae5cebf 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/activity/NotificationClickReceiverActivity.kt @@ -34,7 +34,7 @@ class NotificationClickReceiverActivity : Activity(), TrackableScreen { } private fun handleIntent(data: Intent?) { - if (data == null) { + if (data == null || data.extras == null) { // This should never happen ideally logger.error("Intent is null, cannot process notification click") } else { diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/activity/NotificationClickReceiverActivityTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/activity/NotificationClickReceiverActivityTest.kt index d23867fb5..2f5543cfb 100644 --- a/messagingpush/src/sharedTest/java/io/customer/messagingpush/activity/NotificationClickReceiverActivityTest.kt +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/activity/NotificationClickReceiverActivityTest.kt @@ -1,5 +1,6 @@ package io.customer.messagingpush.activity +import android.content.Intent import android.os.Bundle import androidx.lifecycle.Lifecycle import androidx.test.core.app.ActivityScenario @@ -47,9 +48,10 @@ class NotificationClickReceiverActivityTest : BaseIntegrationTest() { @Test fun clickNotification_givenValidIntent_expectProcessPush() { val extras = pushActivityExtras() + val intent = Intent(context, NotificationClickReceiverActivity::class.java) + intent.putExtras(extras) - val scenario = - ActivityScenario.launch(NotificationClickReceiverActivity::class.java, extras) + val scenario = ActivityScenario.launch(intent) verify(pushMessageProcessorMock).processNotificationClick(any(), any()) scenario.state shouldBeEqualTo Lifecycle.State.DESTROYED @@ -59,9 +61,21 @@ class NotificationClickReceiverActivityTest : BaseIntegrationTest() { fun clickNotification_givenSDKNotInitialized_expectDoNoProcessPush() { val extras = pushActivityExtras() + val intent = Intent(context, NotificationClickReceiverActivity::class.java) + intent.putExtras(extras) + CustomerIO.clearInstance() - val scenario = - ActivityScenario.launch(NotificationClickReceiverActivity::class.java, extras) + val scenario = ActivityScenario.launch(intent) + + verifyNoInteractions(pushMessageProcessorMock) + scenario.state shouldBeEqualTo Lifecycle.State.DESTROYED + } + + @Test + fun clickNotification_givenNullIntent_expectDoNoProcessPush() { + val intent = Intent(context, NotificationClickReceiverActivity::class.java) + + val scenario = ActivityScenario.launch(intent) verifyNoInteractions(pushMessageProcessorMock) scenario.state shouldBeEqualTo Lifecycle.State.DESTROYED From cb49e4c32c5fdb446a876261a76006a096108b1b Mon Sep 17 00:00:00 2001 From: Rehan Date: Fri, 6 Oct 2023 07:38:39 +0500 Subject: [PATCH 16/21] fixed external links --- .../processor/PushMessageProcessorImpl.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt index f4f3dafd3..e34ea7964 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt @@ -145,6 +145,17 @@ internal class PushMessageProcessorImpl( return } + // Check if the deep links can be opened outside the host app + val deepLinkExternalIntent = deepLink?.let { link -> + deepLinkUtil.createDeepLinkExternalIntent(context = activityContext, link = link) + } + // Check if the deep links should be opened externally + if (deepLinkExternalIntent != null) { + // Open link externally and return + activityContext.startActivity(deepLinkExternalIntent) + return + } + // Get the default intent for the host app val defaultHostAppIntent = deepLinkUtil.createDefaultHostAppIntent(context = activityContext) @@ -152,12 +163,7 @@ internal class PushMessageProcessorImpl( val deepLinkHostAppIntent = deepLink?.let { link -> deepLinkUtil.createDeepLinkHostAppIntent(context = activityContext, link = link) } - // Check if the deep links can be opened outside the host app - val deepLinkExternalIntent = deepLink?.let { link -> - deepLinkUtil.createDeepLinkExternalIntent(context = activityContext, link = link) - } val deepLinkIntent: Intent = deepLinkHostAppIntent - ?: deepLinkExternalIntent ?: defaultHostAppIntent ?: return deepLinkIntent.putExtra( From 6f053a0ff78b823e5a4bea6e587a43ebe7a2c80c Mon Sep 17 00:00:00 2001 From: Rehan Date: Fri, 6 Oct 2023 07:44:56 +0500 Subject: [PATCH 17/21] added external link test --- .../processor/PushMessageProcessorTest.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt index 77a3751d8..d5a4f370c 100644 --- a/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt @@ -346,6 +346,22 @@ class PushMessageProcessorTest : BaseTest() { } } + @Test + fun processNotificationClick_givenExternalLink_expectOpenExternalIntent() { + val processor = pushMessageProcessor() + val givenPayload = pushMessagePayload(deepLink = "https://cio.example.com/") + val intent = Intent().apply { + putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) + } + whenever(deepLinkUtilMock.createDeepLinkExternalIntent(any(), any())).thenReturn(Intent()) + + processor.processNotificationClick(context, intent) + + verify(deepLinkUtilMock).createDeepLinkExternalIntent(any(), any()) + verify(deepLinkUtilMock, never()).createDefaultHostAppIntent(any()) + verify(deepLinkUtilMock, never()).createDeepLinkHostAppIntent(any(), any()) + } + @Test fun processNotificationClick_givenPushBehavior_expectResetTaskStack() { setupModuleConfig( From 57af2b6fbb15ad41cb2c550eeb959585dcfd10c4 Mon Sep 17 00:00:00 2001 From: Rehan Date: Fri, 6 Oct 2023 11:49:18 +0500 Subject: [PATCH 18/21] updated default value --- .../io/customer/messagingpush/MessagingPushModuleConfig.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messagingpush/src/main/java/io/customer/messagingpush/MessagingPushModuleConfig.kt b/messagingpush/src/main/java/io/customer/messagingpush/MessagingPushModuleConfig.kt index 8d7d13240..a4bbef9c0 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/MessagingPushModuleConfig.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/MessagingPushModuleConfig.kt @@ -1,7 +1,7 @@ package io.customer.messagingpush import io.customer.messagingpush.config.PushClickBehavior -import io.customer.messagingpush.config.PushClickBehavior.ACTIVITY_NO_FLAGS +import io.customer.messagingpush.config.PushClickBehavior.ACTIVITY_PREVENT_RESTART import io.customer.messagingpush.data.communication.CustomerIOPushNotificationCallback import io.customer.sdk.module.CustomerIOModuleConfig @@ -25,7 +25,7 @@ class MessagingPushModuleConfig private constructor( private var autoTrackPushEvents: Boolean = true private var notificationCallback: CustomerIOPushNotificationCallback? = null private var redirectDeepLinksToOtherApps: Boolean = true - private var pushClickBehavior: PushClickBehavior = ACTIVITY_NO_FLAGS + private var pushClickBehavior: PushClickBehavior = ACTIVITY_PREVENT_RESTART /** * Allows to enable/disable automatic tracking of push events. Auto tracking will generate From 22b10053f6b2e2b953399ee50658b9c0e75b6681 Mon Sep 17 00:00:00 2001 From: Rehan Date: Fri, 6 Oct 2023 11:52:46 +0500 Subject: [PATCH 19/21] fixed tests --- .../io/customer/messagingpush/ModuleMessagingConfigTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/ModuleMessagingConfigTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/ModuleMessagingConfigTest.kt index 7e1d58de4..b6a1dd30c 100644 --- a/messagingpush/src/sharedTest/java/io/customer/messagingpush/ModuleMessagingConfigTest.kt +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/ModuleMessagingConfigTest.kt @@ -55,7 +55,7 @@ internal class ModuleMessagingConfigTest : BaseTest() { moduleConfig.notificationCallback.shouldBeNull() moduleConfig.redirectDeepLinksToOtherApps.shouldBeTrue() - moduleConfig.pushClickBehavior shouldBeEqualTo PushClickBehavior.ACTIVITY_NO_FLAGS + moduleConfig.pushClickBehavior shouldBeEqualTo PushClickBehavior.ACTIVITY_PREVENT_RESTART } @Test @@ -72,7 +72,7 @@ internal class ModuleMessagingConfigTest : BaseTest() { moduleConfig.autoTrackPushEvents.shouldBeTrue() moduleConfig.notificationCallback.shouldBeNull() moduleConfig.redirectDeepLinksToOtherApps.shouldBeTrue() - moduleConfig.pushClickBehavior shouldBeEqualTo PushClickBehavior.ACTIVITY_NO_FLAGS + moduleConfig.pushClickBehavior shouldBeEqualTo PushClickBehavior.ACTIVITY_PREVENT_RESTART } @Test From e64ca3c8395f1d6429a18ec430b9f0d1eab0fbe2 Mon Sep 17 00:00:00 2001 From: Rehan Date: Fri, 6 Oct 2023 14:25:56 +0500 Subject: [PATCH 20/21] improved deep link handling --- .../processor/PushMessageProcessorImpl.kt | 27 ++++++++++--------- .../messagingpush/util/DeepLinkUtil.kt | 4 +-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt index e34ea7964..a3ff1e7a6 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/processor/PushMessageProcessorImpl.kt @@ -145,24 +145,27 @@ internal class PushMessageProcessorImpl( return } - // Check if the deep links can be opened outside the host app - val deepLinkExternalIntent = deepLink?.let { link -> - deepLinkUtil.createDeepLinkExternalIntent(context = activityContext, link = link) + // Check if the deep links are handled within the host app + val deepLinkHostAppIntent = deepLink?.let { link -> + deepLinkUtil.createDeepLinkHostAppIntent(context = activityContext, link = link) } - // Check if the deep links should be opened externally - if (deepLinkExternalIntent != null) { - // Open link externally and return - activityContext.startActivity(deepLinkExternalIntent) - return + // Check if the deep links are handled externally only if the host app doesn't handle it + if (deepLinkHostAppIntent == null) { + // Check if the deep links can be opened outside the host app + val deepLinkExternalIntent = deepLink?.let { link -> + deepLinkUtil.createDeepLinkExternalIntent(context = activityContext, link = link) + } + // Check if the deep links should be opened externally + if (deepLinkExternalIntent != null) { + // Open link externally and return + activityContext.startActivity(deepLinkExternalIntent) + return + } } // Get the default intent for the host app val defaultHostAppIntent = deepLinkUtil.createDefaultHostAppIntent(context = activityContext) - // Check if the deep links are handled within the host app - val deepLinkHostAppIntent = deepLink?.let { link -> - deepLinkUtil.createDeepLinkHostAppIntent(context = activityContext, link = link) - } val deepLinkIntent: Intent = deepLinkHostAppIntent ?: defaultHostAppIntent ?: return diff --git a/messagingpush/src/main/java/io/customer/messagingpush/util/DeepLinkUtil.kt b/messagingpush/src/main/java/io/customer/messagingpush/util/DeepLinkUtil.kt index 589bbb3d5..fe7067d95 100644 --- a/messagingpush/src/main/java/io/customer/messagingpush/util/DeepLinkUtil.kt +++ b/messagingpush/src/main/java/io/customer/messagingpush/util/DeepLinkUtil.kt @@ -26,7 +26,7 @@ interface DeepLinkUtil { * @return intent matching the link in traditional Android way; null if no * matching intents found */ - fun createDeepLinkHostAppIntent(context: Context, link: String?): Intent? + fun createDeepLinkHostAppIntent(context: Context, link: String): Intent? /** * Creates intent outside the host app that can open the provided link. @@ -50,7 +50,7 @@ class DeepLinkUtilImpl( return context.packageManager.getLaunchIntentForPackage(context.packageName) } - override fun createDeepLinkHostAppIntent(context: Context, link: String?): Intent? { + override fun createDeepLinkHostAppIntent(context: Context, link: String): Intent? { val intent: Intent? = queryDeepLinksForHostApp(context, Uri.parse(link)) if (intent == null) { logger.info( From c90b120d68ed6e2468d8de5f0472c7ab0a4fbc87 Mon Sep 17 00:00:00 2001 From: Rehan Date: Fri, 6 Oct 2023 14:36:46 +0500 Subject: [PATCH 21/21] fixed tests --- .../processor/PushMessageProcessorTest.kt | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt b/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt index d5a4f370c..197ff70b3 100644 --- a/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt +++ b/messagingpush/src/sharedTest/java/io/customer/messagingpush/processor/PushMessageProcessorTest.kt @@ -250,7 +250,8 @@ class PushMessageProcessorTest : BaseTest() { fun processNotificationClick_givenValidIntent_expectSuccessfulProcessing() { setupModuleConfig(autoTrackPushEvents = true) val processor = pushMessageProcessor() - val givenPayload = pushMessagePayload(deepLink = "https://cio.example.com/") + val givenDeepLink = "https://cio.example.com/" + val givenPayload = pushMessagePayload(deepLink = givenDeepLink) val intent = Intent().apply { putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) } @@ -262,9 +263,9 @@ class PushMessageProcessorTest : BaseTest() { MetricEvent.opened, givenPayload.cioDeliveryToken ) + verify(deepLinkUtilMock).createDeepLinkHostAppIntent(context, givenDeepLink) + verify(deepLinkUtilMock).createDeepLinkExternalIntent(context, givenDeepLink) verify(deepLinkUtilMock).createDefaultHostAppIntent(context) - verify(deepLinkUtilMock).createDeepLinkHostAppIntent(context, givenPayload.deepLink) - verify(deepLinkUtilMock).createDeepLinkExternalIntent(context, givenPayload.deepLink!!) } @Test @@ -291,9 +292,9 @@ class PushMessageProcessorTest : BaseTest() { processor.processNotificationClick(context, intent) - verify(deepLinkUtilMock).createDefaultHostAppIntent(any()) verify(deepLinkUtilMock, never()).createDeepLinkHostAppIntent(any(), any()) verify(deepLinkUtilMock, never()).createDeepLinkExternalIntent(any(), any()) + verify(deepLinkUtilMock).createDefaultHostAppIntent(any()) } @Test @@ -357,9 +358,26 @@ class PushMessageProcessorTest : BaseTest() { processor.processNotificationClick(context, intent) + verify(deepLinkUtilMock).createDeepLinkHostAppIntent(any(), any()) verify(deepLinkUtilMock).createDeepLinkExternalIntent(any(), any()) verify(deepLinkUtilMock, never()).createDefaultHostAppIntent(any()) - verify(deepLinkUtilMock, never()).createDeepLinkHostAppIntent(any(), any()) + } + + @Test + fun processNotificationClick_givenInternalLink_expectOpenInternalIntent() { + val processor = pushMessageProcessor() + val givenPayload = pushMessagePayload(deepLink = "https://cio.example.com/") + val intent = Intent().apply { + putExtra(NotificationClickReceiverActivity.NOTIFICATION_PAYLOAD_EXTRA, givenPayload) + } + whenever(deepLinkUtilMock.createDeepLinkExternalIntent(any(), any())).thenReturn(Intent()) + whenever(deepLinkUtilMock.createDeepLinkHostAppIntent(any(), any())).thenReturn(Intent()) + + processor.processNotificationClick(context, intent) + + verify(deepLinkUtilMock).createDeepLinkHostAppIntent(any(), any()) + verify(deepLinkUtilMock, never()).createDeepLinkExternalIntent(any(), any()) + verify(deepLinkUtilMock).createDefaultHostAppIntent(any()) } @Test