diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushNotificationUtil.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushNotificationUtil.java index 0aa1e5d17..138d77450 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushNotificationUtil.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterablePushNotificationUtil.java @@ -18,7 +18,12 @@ static boolean processPendingAction(Context context) { boolean handled = false; if (pendingAction != null) { handled = executeAction(context, pendingAction); - pendingAction = null; + // Only clear pending action if it was handled. + // This allows the action to be processed later when SDK is fully initialized + // (e.g., when customActionHandler becomes available after initialize() is called). + if (handled) { + pendingAction = null; + } } return handled; } @@ -38,6 +43,13 @@ static void handlePushAction(Context context, Intent intent) { IterableLogger.e(TAG, "handlePushAction: extras == null, can't handle push action"); return; } + + // Store the application context if not already set, so custom actions can be + // processed even when the SDK hasn't been fully initialized (e.g., openApp=false) + if (IterableApi.sharedInstance._applicationContext == null && context != null) { + IterableApi.sharedInstance._applicationContext = context.getApplicationContext(); + } + IterableNotificationData notificationData = new IterableNotificationData(intent.getExtras()); String actionIdentifier = intent.getStringExtra(IterableConstants.ITERABLE_DATA_ACTION_IDENTIFIER); IterableAction action = null; diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushActionReceiverTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushActionReceiverTest.java index 82a6f20ed..b58a0c97f 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushActionReceiverTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterablePushActionReceiverTest.java @@ -159,5 +159,59 @@ public void testLegacyDeepLinkPayload() throws Exception { assertEquals("https://example.com", capturedAction.getValue().getData()); } + @Test + public void testBackgroundCustomActionWithNonInitializedSDK() throws Exception { + // Reset to simulate SDK not being initialized + IterableTestUtils.resetIterableApi(); + + // Verify context is initially null + assertNull(IterableApi.sharedInstance._applicationContext); + + IterablePushActionReceiver iterablePushActionReceiver = new IterablePushActionReceiver(); + Intent intent = new Intent(IterableConstants.ACTION_PUSH_ACTION); + intent.putExtra(IterableConstants.ITERABLE_DATA_ACTION_IDENTIFIER, "remindMeButton"); + intent.putExtra(IterableConstants.ITERABLE_DATA_KEY, IterableTestUtils.getResourceString("push_payload_background_custom_action.json")); + + // Receive push action when SDK is not initialized + iterablePushActionReceiver.onReceive(ApplicationProvider.getApplicationContext(), intent); + + // Verify that context was stored even without SDK initialization + assertNotNull(IterableApi.sharedInstance._applicationContext); + + // Verify that the main app activity was NOT launched (openApp=false) + Application application = ApplicationProvider.getApplicationContext(); + Intent activityIntent = shadowOf(application).peekNextStartedActivity(); + assertNull(activityIntent); + } + + @Test + public void testBackgroundCustomActionProcessedAfterSDKInit() throws Exception { + // Reset to simulate SDK not being initialized + IterableTestUtils.resetIterableApi(); + + IterablePushActionReceiver iterablePushActionReceiver = new IterablePushActionReceiver(); + Intent intent = new Intent(IterableConstants.ACTION_PUSH_ACTION); + intent.putExtra(IterableConstants.ITERABLE_DATA_ACTION_IDENTIFIER, "remindMeButton"); + intent.putExtra(IterableConstants.ITERABLE_DATA_KEY, IterableTestUtils.getResourceString("push_payload_background_custom_action.json")); + + // Receive push action when SDK is not initialized (action won't be handled) + iterablePushActionReceiver.onReceive(ApplicationProvider.getApplicationContext(), intent); + + // Now initialize SDK with a custom action handler + stubAnyRequestReturningStatusCode(server, 200, "{}"); + final boolean[] handlerCalled = {false}; + IterableTestUtils.createIterableApiNew(builder -> + builder.setCustomActionHandler((action, actionContext) -> { + handlerCalled[0] = true; + assertEquals("snoozeReminder", action.getType()); + return true; + }) + ); + + // Verify that the custom action handler was called during initialization + // (processPendingAction is called in initialize()) + assertEquals(true, handlerCalled[0]); + } + } diff --git a/iterableapi/src/test/resources/push_payload_background_custom_action.json b/iterableapi/src/test/resources/push_payload_background_custom_action.json new file mode 100644 index 000000000..6cdfeb62f --- /dev/null +++ b/iterableapi/src/test/resources/push_payload_background_custom_action.json @@ -0,0 +1,20 @@ +{ + "campaignId": 5678, + "templateId": 8765, + "messageId": "background123456", + "isGhostPush": false, + "actionButtons": [ + { + "identifier": "remindMeButton", + "title": "Remind me in 15 minutes", + "openApp": false, + "action": { + "type": "snoozeReminder", + "data": "{\"delay\":15}" + } + } + ], + "defaultAction": { + "type": null + } +}