Skip to content

Properly handle in-app messages dismissed by back press #2328

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

nan-li
Copy link
Contributor

@nan-li nan-li commented Jun 26, 2025

Description

One Line Summary

Properly handle and dismiss in-app messages that are dismissed by back press.

Details

Motivation

Fixes poor behavior: When back button is pressed, the system automatically dismisses the in-app message, without the SDK's awareness. This leads to poor user experience as the SDK does not know of this message's dismissal and will inappropriately redisplay it.

Scope

  • Create new OSPopupWindow class extending PopupWindow
  • Adds additional code to trigger post-dismissal flows when we believe the system dismissed the IAM without our awareness
  • All new logic lives in InAppMessageView. A single instance of InAppMessageView can have multiple popupWindow instances such as when orientation changes. So, we need to hook into individual popup windows instead of the View. We mark the popup window as manually dismissed in the method removeAllViews(). This is sufficient as this method is called by every flow in the SDK that dismisses an IAM (clicking to close, activity change, drag to dismiss, etc). In our listener, if we believe the popup window was dismissed by the system bypassing OneSignal, we trigger messageController?.onMessageWasDismissed(). This call is appropriate as this is the root method that triggers the post-dismissal flows for an IAM. We considered doing more logic in the dismiss event but we cannot generalize here as some dismiss events should not trigger the post-dismissal flow, such as on orientation changes.

Example of system triggering popup window dismissal

 java.lang.Thread.State: RUNNABLE
	  at com.onesignal.inAppMessages.internal.display.impl.InAppMessageView$popupWindowListener$1.onDismiss(InAppMessageView.kt:91)
	  at com.onesignal.inAppMessages.internal.display.impl.OSPopupWindow.dismiss(OSPopupWindow.kt:32)
	  at android.widget.PopupWindow$PopupDecorView.dispatchKeyEvent(PopupWindow.java:2555)
	  at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:6584)
	  at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:6450)
	  at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5910)
	  at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5967)
	  at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5933)
	  at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:6098)
	  at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5941)
	  at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:6155)
	  at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5914)
	  at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5967)
	  at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5933)
	  at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5941)
	  at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5914)
	  at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5967)
	  at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5933)
	  at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:6131)
	  at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:6311)
	  at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:3649)
	  at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:3169)
	  at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:3160)
	  at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:3626)
	  at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:154)
	  at android.os.MessageQueue.nativePollOnce(MessageQueue.java:-1)
	  at android.os.MessageQueue.next(MessageQueue.java:335)
	  at android.os.Looper.loopOnce(Looper.java:161)
	  at android.os.Looper.loop(Looper.java:288)
	  at android.app.ActivityThread.main(ActivityThread.java:7872)
	  at java.lang.reflect.Method.invoke(Method.java:-1)
	  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
	  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

Testing

Unit testing

None

Manual testing

Android emulator API 33

Types of IAMs tested:

  • Standard modals
  • Full screen
  • Banner (note banners are not dismissed by the system on back press and remain on screen)

Scenarios tested:

  • Device orientation change (correctly does not trigger our listener)
  • Backgrounding and foregrounding app (correctly does not trigger our listener)
  • Click on buttons in IAM (correctly does not trigger our listener)
  • Drag to dismiss (correctly does not trigger our listener)
  • Click on button with URL (correctly does not trigger our listener)
  • Click on button that triggers a push prompt (correctly does not trigger our listener)
  • Back button press (correctly does trigger our listener)

Affected code checklist

  • Notifications
    • Display
    • Open
    • Push Processing
    • Confirm Deliveries
  • Outcomes
  • Sessions
  • In-App Messaging
  • REST API requests
  • Public API changes

Checklist

Overview

  • I have filled out all REQUIRED sections above
  • PR does one thing
  • Any Public API changes are explained in the PR details and conform to existing APIs

Testing

  • I have included test coverage for these changes, or explained why they are not needed
  • All automated tests pass, or I explained why that is not possible
  • I have personally tested this on my device, or explained why that is not possible

Final pass

  • Code is as readable as possible.
  • I have reviewed this PR myself, ensuring it meets each checklist item

This change is Reviewable

@nan-li nan-li requested review from jkasten2 and jinliu9508 June 26, 2025 23:38
@nan-li nan-li force-pushed the iam_back_press branch 2 times, most recently from 997a21d to 222c20b Compare June 26, 2025 23:46
* By extending PopupWindow, we can mark it as dismissed by us, and we can add a custom listener to the dismiss event and receive this flag.
* In InAppMessageView which owns the popup window, it will listen to the popup window's dismissal event and trigger the post-dismissal flow. It also sets the manual flag when `removeAllViews()` is invoked.
Copy link
Member

@jkasten2 jkasten2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested on Android 9 (API level 28) and if I press the back button it does dismiss the IAM, however if I background the app and resume the app it comes back.

  • If instead I tap the "X", the IAM doesn't come back, as I would expect.
  • I didn't see this issue when I tested on Android 14 (API level 34)

After more testing, this doesn't seem related, and also doesn't consistently happen.

Copy link
Member

@jkasten2 jkasten2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code changes look good. The only thing I would like to see is testing a few more versions of Android and including them in the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants