From a6638e428b65d83efd12bfc2a51f5a7d17543063 Mon Sep 17 00:00:00 2001 From: Simon Bullen Date: Tue, 24 Jun 2025 14:23:41 +1000 Subject: [PATCH 1/2] Adds CustomInAppDisplayProvider & InAppController support to allow applications to display CTInAppNotifications themselves.. --- .../android/sdk/inapp/InAppController.java | 47 ++++++++++++++ .../CustomInAppDisplayProvider.java | 63 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 clevertap-core/src/main/java/com/clevertap/android/sdk/interfaces/CustomInAppDisplayProvider.java diff --git a/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/InAppController.java b/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/InAppController.java index 602b24953..ac6d727bc 100644 --- a/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/InAppController.java +++ b/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/InAppController.java @@ -42,6 +42,7 @@ import com.clevertap.android.sdk.inapp.store.preference.FileStore; import com.clevertap.android.sdk.inapp.store.preference.InAppAssetsStore; import com.clevertap.android.sdk.inapp.store.preference.StoreRegistry; +import com.clevertap.android.sdk.interfaces.CustomInAppDisplayProvider; import com.clevertap.android.sdk.network.NetworkManager; import com.clevertap.android.sdk.task.CTExecutorFactory; import com.clevertap.android.sdk.task.MainLooperHandler; @@ -794,6 +795,10 @@ private void showInApp( currentlyDisplayingInApp = inAppNotification; + if (tryCustomShowInApp(inAppNotification)) { + return; // handled by a CustomInAppDisplayProvider + } + CTInAppBaseFragment inAppFragment = null; CTInAppType type = inAppNotification.getInAppType(); switch (type) { @@ -872,6 +877,48 @@ private void showInApp( } } + // Tries to show the notification via a CustomInAppDisplayProvider implemented by the current Activity. + // Returns true if shown, false otherwise. + private boolean tryCustomShowInApp(CTInAppNotification notification) { + Activity activity = CoreMetaData.getCurrentActivity(); + if (activity == null || !(activity instanceof CustomInAppDisplayProvider) ) { + return false; // not a CustomInAppDisplayProvider + } + CustomInAppDisplayProvider displayProvider = (CustomInAppDisplayProvider) activity; + if (!displayProvider.canDisplay(notification, config, activity)) { + return false; // not supported/wanted + } + displayProvider.display(notification, config, activity, new CustomInAppDisplayProvider.Callbacks() { + @Override + public Bundle onActionTriggered(@NonNull CTInAppNotification notification, + @NonNull CTInAppAction action, + @NonNull String callToAction, + Bundle additionalData, + Context activityContext) { + return inAppNotificationActionTriggered(notification, action, callToAction, additionalData, + activityContext); + } + + @Override + public void onButtonClicked(@NonNull CTInAppNotification notification, CTInAppNotificationButton button, + Context activityContext) { + inAppNotificationDidClick(notification, button, activityContext); + } + + @Override + public void onDismissed(@NonNull CTInAppNotification notification, Bundle formData) { + inAppNotificationDidDismiss(notification, formData); + } + + @Override + public void onShown(@NonNull CTInAppNotification notification, Bundle formData) { + inAppNotificationDidShow(notification, formData); + } + }); + + return true; // handled + } + @WorkerThread public void onQueueEvent(final String eventName, Map eventProperties, Location userLocation) { final Map appFieldsWithEventProperties = JsonUtil.mapFromJson( diff --git a/clevertap-core/src/main/java/com/clevertap/android/sdk/interfaces/CustomInAppDisplayProvider.java b/clevertap-core/src/main/java/com/clevertap/android/sdk/interfaces/CustomInAppDisplayProvider.java new file mode 100644 index 000000000..0f995e429 --- /dev/null +++ b/clevertap-core/src/main/java/com/clevertap/android/sdk/interfaces/CustomInAppDisplayProvider.java @@ -0,0 +1,63 @@ +package com.clevertap.android.sdk.interfaces; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import androidx.annotation.NonNull; +import com.clevertap.android.sdk.CleverTapInstanceConfig; +import com.clevertap.android.sdk.inapp.CTInAppAction; +import com.clevertap.android.sdk.inapp.CTInAppNotification; +import com.clevertap.android.sdk.inapp.CTInAppNotificationButton; + +// +// CustomInAppDisplayProvider +// -------------------------- +// Activities that implement this interface can take responsibility for +// displaying individual CTInAppNotifications. +// +// InAppController looks for this interface on the current activity whenever it +// needs to show an in-app notification. If present, it calls canDisplay(); if +// that returns true it then calls display(). +// +// display() MUST report its outcome via the Callbacks object so the SDK keeps +// queue state and analytics accurate. +// +public interface CustomInAppDisplayProvider { + + // Returns true if this provider can handle the notification in this host. + boolean canDisplay( + @NonNull CTInAppNotification notification, + @NonNull CleverTapInstanceConfig config, + @NonNull Activity host); + + // Renders the notification (only after canDisplay() == true). + // Implementation MUST report its outcome via Callbacks. + void display( + @NonNull CTInAppNotification notification, + @NonNull CleverTapInstanceConfig config, + @NonNull Activity host, + @NonNull Callbacks callbacks); + + // Bridge back into InAppController for metrics & queue management. + interface Callbacks { + Bundle onActionTriggered( + @NonNull CTInAppNotification notification, + @NonNull CTInAppAction action, + @NonNull String callToAction, + Bundle additionalData, + Context activityContext); + + void onButtonClicked( + @NonNull CTInAppNotification notification, + CTInAppNotificationButton button, + Context activityContext); + + void onDismissed( + @NonNull CTInAppNotification notification, + Bundle formData); + + void onShown( + @NonNull CTInAppNotification notification, + Bundle formData); + } +} From 86425715a54e3d924b382aa296c61a4e4c69eaf5 Mon Sep 17 00:00:00 2001 From: Simon Bullen Date: Tue, 24 Jun 2025 14:52:16 +1000 Subject: [PATCH 2/2] Handle errors in providers --- .../android/sdk/inapp/InAppController.java | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/InAppController.java b/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/InAppController.java index ac6d727bc..6600a3eb8 100644 --- a/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/InAppController.java +++ b/clevertap-core/src/main/java/com/clevertap/android/sdk/inapp/InAppController.java @@ -884,37 +884,42 @@ private boolean tryCustomShowInApp(CTInAppNotification notification) { if (activity == null || !(activity instanceof CustomInAppDisplayProvider) ) { return false; // not a CustomInAppDisplayProvider } - CustomInAppDisplayProvider displayProvider = (CustomInAppDisplayProvider) activity; - if (!displayProvider.canDisplay(notification, config, activity)) { - return false; // not supported/wanted - } - displayProvider.display(notification, config, activity, new CustomInAppDisplayProvider.Callbacks() { - @Override - public Bundle onActionTriggered(@NonNull CTInAppNotification notification, - @NonNull CTInAppAction action, - @NonNull String callToAction, - Bundle additionalData, - Context activityContext) { - return inAppNotificationActionTriggered(notification, action, callToAction, additionalData, - activityContext); + try { + CustomInAppDisplayProvider displayProvider = (CustomInAppDisplayProvider) activity; + if (!displayProvider.canDisplay(notification, config, activity)) { + return false; // not supported/wanted } + displayProvider.display(notification, config, activity, new CustomInAppDisplayProvider.Callbacks() { + @Override + public Bundle onActionTriggered(@NonNull CTInAppNotification notification, + @NonNull CTInAppAction action, + @NonNull String callToAction, + Bundle additionalData, + Context activityContext) { + return inAppNotificationActionTriggered(notification, action, callToAction, additionalData, + activityContext); + } - @Override - public void onButtonClicked(@NonNull CTInAppNotification notification, CTInAppNotificationButton button, - Context activityContext) { - inAppNotificationDidClick(notification, button, activityContext); - } + @Override + public void onButtonClicked(@NonNull CTInAppNotification notification, CTInAppNotificationButton button, + Context activityContext) { + inAppNotificationDidClick(notification, button, activityContext); + } - @Override - public void onDismissed(@NonNull CTInAppNotification notification, Bundle formData) { - inAppNotificationDidDismiss(notification, formData); - } + @Override + public void onDismissed(@NonNull CTInAppNotification notification, Bundle formData) { + inAppNotificationDidDismiss(notification, formData); + } - @Override - public void onShown(@NonNull CTInAppNotification notification, Bundle formData) { - inAppNotificationDidShow(notification, formData); - } - }); + @Override + public void onShown(@NonNull CTInAppNotification notification, Bundle formData) { + inAppNotificationDidShow(notification, formData); + } + }); + } catch (Exception e) { + Logger.d(config.getAccountId(), "Error in custom in-app display provider: " + e.getMessage(), e); + return false; // fall back to standard display + } return true; // handled }