From c50a2b5c04e761f9039ae2d4d089d9c3f19d7d6b Mon Sep 17 00:00:00 2001 From: Vassil Angelov <148857285+vasct@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:40:50 +0200 Subject: [PATCH] refactor(MC-2362): Add parsing of url params on open-url action (#684) --- .../sdk/inapp/CTInAppBaseFragment.java | 58 ++++-- .../sdk/inapp/CTInAppBaseFragmentTest.kt | 171 ++++++++++++++++++ 2 files changed, 209 insertions(+), 20 deletions(-) create mode 100644 clevertap-core/src/test/java/com/clevertap/android/sdk/inapp/CTInAppBaseFragmentTest.kt diff --git a/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/CTInAppBaseFragment.java b/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/CTInAppBaseFragment.java index 06b9b5bfe..595df1d2d 100644 --- a/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/CTInAppBaseFragment.java +++ b/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/CTInAppBaseFragment.java @@ -14,6 +14,8 @@ import com.clevertap.android.sdk.customviews.CloseImageView; import com.clevertap.android.sdk.inapp.images.FileResourceProvider; import com.clevertap.android.sdk.utils.UriHelper; + +import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.net.URLDecoder; import java.util.concurrent.atomic.AtomicBoolean; @@ -81,31 +83,47 @@ public void triggerAction( @NonNull CTInAppAction action, @Nullable String callToAction, @Nullable Bundle additionalData) { + if (action.getType() == InAppActionType.OPEN_URL) { + //All URL parameters should be tracked as additional data + final Bundle urlActionData = UriHelper.getAllKeyValuePairs(action.getActionUrl(), false); + + // callToAction is handled as a parameter + String callToActionUrlParam = urlActionData.getString(Constants.KEY_C2A); + // no need to keep it in the data bundle + urlActionData.remove(Constants.KEY_C2A); + + // add all additional params, overriding the url params if there is a collision + if (additionalData != null) { + urlActionData.putAll(additionalData); + } + // Use the merged data for the action + additionalData = urlActionData; + if (callToActionUrlParam != null) { + // check if there is a deeplink within the callToAction param + final String[] parts = callToActionUrlParam.split(Constants.URL_PARAM_DL_SEPARATOR); + if (parts.length == 2) { + // Decode it here as it is not decoded by UriHelper + try { + // Extract the actual callToAction value + callToActionUrlParam = URLDecoder.decode(parts[0], "UTF-8"); + } catch (UnsupportedEncodingException | IllegalArgumentException e) { + config.getLogger().debug("Error parsing c2a param", e); + } + // use the url from the callToAction param + action = CTInAppAction.createOpenUrlAction(parts[1]); + } + } + if (callToAction == null) { + // Use the url param value only if no other value is passed + callToAction = callToActionUrlParam; + } + } Bundle actionData = notifyActionTriggered(action, callToAction != null ? callToAction : "", additionalData); didDismiss(actionData); } void openActionUrl(String url) { - try { - final Bundle formData = UriHelper.getAllKeyValuePairs(url, false); - - String callToAction = formData.getString(Constants.KEY_C2A); - if (callToAction != null) { - final String[] parts = callToAction.split(Constants.URL_PARAM_DL_SEPARATOR); - if (parts.length == 2) { - // Decode it here as wzrk_c2a is not decoded by UriHelper - callToAction = URLDecoder.decode(parts[0], "UTF-8"); - formData.putString(Constants.KEY_C2A, callToAction); - url = parts[1]; - } - } - - CTInAppAction action = CTInAppAction.createOpenUrlAction(url); - config.getLogger().debug("Executing call to action for in-app: " + url); - triggerAction(action, callToAction != null ? callToAction : "", formData); - } catch (Throwable t) { - config.getLogger().debug("Error parsing the in-app notification action!", t); - } + triggerAction(CTInAppAction.createOpenUrlAction(url), null, null); } public void didDismiss(Bundle data) { diff --git a/clevertap-core/src/test/java/com/clevertap/android/sdk/inapp/CTInAppBaseFragmentTest.kt b/clevertap-core/src/test/java/com/clevertap/android/sdk/inapp/CTInAppBaseFragmentTest.kt new file mode 100644 index 000000000..648c37de9 --- /dev/null +++ b/clevertap-core/src/test/java/com/clevertap/android/sdk/inapp/CTInAppBaseFragmentTest.kt @@ -0,0 +1,171 @@ +package com.clevertap.android.sdk.inapp + +import android.net.Uri +import android.os.Bundle +import com.clevertap.android.sdk.Constants +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class CTInAppBaseFragmentTest { + + @Test + fun `triggerAction should parse url parameters as additionalData`() { + val fragment = spyk() + val inAppListener = mockk(relaxed = true) + every { fragment.listener } returns inAppListener + + val param1 = "value" + val param2 = "value 2" + val param3 = "5" + val url = Uri.parse("https://clevertap.com") + .buildUpon() + .appendQueryParameter("param1", param1) + .appendQueryParameter("param2", param2) + .appendQueryParameter("param3", param3) + .build().toString() + + fragment.triggerAction(CTInAppAction.createOpenUrlAction(url), null, null) + verify { + inAppListener.inAppNotificationActionTriggered( + inAppNotification = any(), + action = match { action -> + action.type == InAppActionType.OPEN_URL + && action.actionUrl == url + }, + callToAction = any(), + additionalData = match { data -> + param1 == data.getString("param1") + && param2 == data.getString("param2") + && param3 == data.getString("param3") + }, + activityContext = any() + ) + } + } + + @Test + fun `triggerAction should merge url parameters with provided additionalData `() { + val fragment = spyk() + val inAppListener = mockk(relaxed = true) + every { fragment.listener } returns inAppListener + + val urlParam1 = "value" + val urlParam2 = "value 2" + val urlParam3 = "5" + val url = Uri.parse("https://clevertap.com") + .buildUpon() + .appendQueryParameter("param1", urlParam1) + .appendQueryParameter("param2", urlParam2) + .appendQueryParameter("param3", urlParam3) + .build().toString() + + val dataParam1 = "dataValue" + val dataParam2 = "data value 2" + val data = Bundle().apply { + putString("param1", dataParam1) + putString("param2", dataParam2) + } + + fragment.triggerAction(CTInAppAction.createOpenUrlAction(url), null, data) + verify { + inAppListener.inAppNotificationActionTriggered( + inAppNotification = any(), + action = any(), + callToAction = any(), + additionalData = match { data -> + dataParam1 == data.getString("param1") + && dataParam2 == data.getString("param2") + && urlParam3 == data.getString("param3") + }, + activityContext = any() + ) + } + } + + @Test + fun `triggerAction should use callToAction argument or c2a url param`() { + val fragment = spyk() + val inAppListener = mockk(relaxed = true) + every { fragment.listener } returns inAppListener + + val callToActionParam = "c2aParam" + val url = Uri.parse("https://clevertap.com") + .buildUpon() + .appendQueryParameter(Constants.KEY_C2A, callToActionParam) + .build().toString() + + fragment.triggerAction(CTInAppAction.createOpenUrlAction(url), null, null) + verify { + inAppListener.inAppNotificationActionTriggered( + inAppNotification = any(), + action = any(), + callToAction = callToActionParam, + additionalData = any(), + activityContext = any() + ) + } + + val callToActionArgument = "argument" + fragment.triggerAction(CTInAppAction.createOpenUrlAction(url), callToActionArgument, null) + verify { + inAppListener.inAppNotificationActionTriggered( + inAppNotification = any(), + action = any(), + callToAction = callToActionArgument, + additionalData = any(), + activityContext = any() + ) + } + } + + @Test + fun `triggerAction should parse c2a url param with __dl__ data`() { + val fragment = spyk() + val inAppListener = mockk(relaxed = true) + every { fragment.listener } returns inAppListener + + val dl = "https://deeplink.com?param1=asd¶m2=value2" + val callToActionParam = "c2aParam" + val param1 = "value" + val url = Uri.parse("https://clevertap.com") + .buildUpon() + .appendQueryParameter(Constants.KEY_C2A, "${callToActionParam}__dl__$dl") + .appendQueryParameter("param1", param1) + .build().toString() + + fragment.triggerAction(CTInAppAction.createOpenUrlAction(url), null, null) + verify { + inAppListener.inAppNotificationActionTriggered( + inAppNotification = any(), + action = any(), + callToAction = callToActionParam, + additionalData = match { data -> + data.size() == 1 + && param1 == data.getString("param1") + }, + activityContext = any() + ) + } + + val callToActionArgument = "argument" + fragment.triggerAction(CTInAppAction.createOpenUrlAction(url), callToActionArgument, null) + verify { + inAppListener.inAppNotificationActionTriggered( + inAppNotification = any(), + action = any(), + callToAction = callToActionArgument, + additionalData = match { data -> + data.size() == 1 + && param1 == data.getString("param1") + }, + activityContext = any() + ) + } + } +}