Skip to content

Commit 3e55686

Browse files
cursoragentabdul
andcommitted
Fix IAM display rendering issues on foldable Android devices
This commit addresses rendering issues with In-App Messages (IAM) on foldable Android devices like Samsung Galaxy Fold/Flip. All changes are gated behind a feature flag (SDK_050800_FOLDABLE_IAM_FIX) that can be enabled remotely. When the flag is OFF, the SDK uses legacy behavior. When ON, the new foldable-aware behavior is activated. Root cause: - Foldable devices change screen size without triggering orientation changes - The SDK only listened for CONFIG_ORIENTATION changes, missing fold/unfold events - ViewUtils used deprecated APIs that don't properly handle multi-window scenarios Changes: 1. FoldableIAMFeature.kt (NEW): - Global feature switch for foldable device IAM improvements - Controlled by FeatureManager via remote config 2. FeatureFlag.kt: - Added SDK_050800_FOLDABLE_IAM_FIX feature flag - Uses IMMEDIATE activation mode for instant toggling 3. FeatureManager.kt: - Added side effect handler for the new feature flag - Updates FoldableIAMFeature.isEnabled when flag changes 4. ViewUtils.kt: - getWindowHeight(): Uses WindowMetrics API (API 30+) when FF enabled - getWindowWidth(): Uses WindowMetrics API (API 30+) when FF enabled - getFullbleedWindowWidth(): Uses WindowMetrics API (API 30+) when FF enabled - Falls back to legacy behavior when FF disabled 5. ApplicationService.kt: - Screen size change detection only runs when FF enabled - Detects fold/unfold via screenWidthDp/screenHeightDp changes - Triggers IAM view recreation on screen size change 6. WebViewManager.kt: - Updated comment to clarify fold/unfold handling The fix ensures IAMs are properly resized and repositioned when users fold/unfold their devices, preventing cut-off content and mispositioned messages - but only when the feature flag is enabled. Co-authored-by: abdul <abdul@onesignal.com>
1 parent 1bc6dc4 commit 3e55686

File tree

6 files changed

+127
-1
lines changed

6 files changed

+127
-1
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.onesignal.common
2+
3+
import com.onesignal.debug.internal.logging.Logging
4+
5+
/**
6+
* Global feature switch for foldable device IAM display improvements.
7+
* When enabled, uses modern WindowMetrics API and detects screen size changes.
8+
*/
9+
internal object FoldableIAMFeature {
10+
@Volatile
11+
var isEnabled: Boolean = false
12+
private set
13+
14+
fun updateEnabled(
15+
enabled: Boolean,
16+
source: String,
17+
) {
18+
val previous = isEnabled
19+
isEnabled = enabled
20+
21+
if (previous != enabled) {
22+
Logging.info("OneSignal: FoldableIAMFeature changed to isEnabled=$enabled (source=$source)")
23+
} else {
24+
Logging.debug("OneSignal: FoldableIAMFeature unchanged (isEnabled=$enabled, source=$source)")
25+
}
26+
}
27+
}

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/ViewUtils.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,35 @@ object ViewUtils {
1919
// Due to differences in accounting for keyboard, navigation bar, and status bar between
2020
// Android versions have different implementation here
2121
fun getWindowHeight(activity: Activity): Int {
22+
// When foldable IAM fix is enabled and API 30+, use WindowMetrics for accurate dimensions
23+
if (FoldableIAMFeature.isEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
24+
return getWindowHeightAPI30Plus(activity)
25+
}
26+
// Legacy behavior
2227
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
2328
getWindowHeightAPI23Plus(activity)
2429
} else {
2530
getWindowHeightLollipop(activity)
2631
}
2732
}
2833

34+
@Suppress("DEPRECATION")
2935
private fun getDisplaySizeY(activity: Activity): Int {
3036
val point = Point()
3137
activity.windowManager.defaultDisplay.getSize(point)
3238
return point.y
3339
}
3440

41+
@TargetApi(Build.VERSION_CODES.R)
42+
private fun getWindowHeightAPI30Plus(activity: Activity): Int {
43+
val windowMetrics = activity.windowManager.currentWindowMetrics
44+
val insets =
45+
windowMetrics.windowInsets.getInsetsIgnoringVisibility(
46+
android.view.WindowInsets.Type.systemBars(),
47+
)
48+
return windowMetrics.bounds.height() - insets.top - insets.bottom
49+
}
50+
3551
// Requirement: Ensure DecorView is ready by using OSViewUtils.decorViewReady
3652
@TargetApi(Build.VERSION_CODES.M)
3753
private fun getWindowHeightAPI23Plus(activity: Activity): Int {
@@ -61,6 +77,7 @@ object ViewUtils {
6177
return rect
6278
}
6379

80+
@Suppress("DEPRECATION")
6481
fun getCutoutAndStatusBarInsets(activity: Activity): IntArray {
6582
val frame = getWindowVisibleDisplayFrame(activity)
6683
val contentView = activity.window.findViewById<View>(Window.ID_ANDROID_CONTENT)
@@ -87,6 +104,12 @@ object ViewUtils {
87104
}
88105

89106
fun getFullbleedWindowWidth(activity: Activity): Int {
107+
// When foldable IAM fix is enabled and API 30+, use WindowMetrics for accurate dimensions
108+
if (FoldableIAMFeature.isEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
109+
val windowMetrics = activity.windowManager.currentWindowMetrics
110+
return windowMetrics.bounds.width()
111+
}
112+
// Legacy behavior
90113
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
91114
val decorView = activity.window.decorView
92115
decorView.width
@@ -96,6 +119,21 @@ object ViewUtils {
96119
}
97120

98121
fun getWindowWidth(activity: Activity): Int {
122+
// When foldable IAM fix is enabled and API 30+, use WindowMetrics for accurate dimensions
123+
if (FoldableIAMFeature.isEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
124+
return getWindowWidthAPI30Plus(activity)
125+
}
126+
// Legacy behavior
99127
return getWindowVisibleDisplayFrame(activity).width()
100128
}
129+
130+
@TargetApi(Build.VERSION_CODES.R)
131+
private fun getWindowWidthAPI30Plus(activity: Activity): Int {
132+
val windowMetrics = activity.windowManager.currentWindowMetrics
133+
val insets =
134+
windowMetrics.windowInsets.getInsetsIgnoringVisibility(
135+
android.view.WindowInsets.Type.systemBars(),
136+
)
137+
return windowMetrics.bounds.width() - insets.left - insets.right
138+
}
101139
}

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/application/impl/ApplicationService.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment
1515
import androidx.fragment.app.FragmentManager
1616
import com.onesignal.common.AndroidUtils
1717
import com.onesignal.common.DeviceUtils
18+
import com.onesignal.common.FoldableIAMFeature
1819
import com.onesignal.common.events.EventProducer
1920
import com.onesignal.common.threading.Waiter
2021
import com.onesignal.core.internal.application.ActivityLifecycleHandlerBase
@@ -83,6 +84,9 @@ class ApplicationService() : IApplicationService, ActivityLifecycleCallbacks, On
8384

8485
val configuration =
8586
object : ComponentCallbacks {
87+
private var lastScreenWidthDp: Int = 0
88+
private var lastScreenHeightDp: Int = 0
89+
8690
override fun onConfigurationChanged(newConfig: Configuration) {
8791
// If Activity contains the configChanges orientation flag, re-create the view this way
8892
if (current != null &&
@@ -93,6 +97,28 @@ class ApplicationService() : IApplicationService, ActivityLifecycleCallbacks, On
9397
) {
9498
onOrientationChanged(newConfig.orientation, current!!)
9599
}
100+
101+
// Handle foldable device screen size changes (fold/unfold events)
102+
// Only enabled when FoldableIAMFeature is on
103+
// Foldable devices trigger CONFIG_SCREEN_SIZE without orientation change
104+
if (FoldableIAMFeature.isEnabled && current != null && hasScreenSizeChanged(newConfig)) {
105+
Logging.debug(
106+
"ApplicationService.onConfigurationChanged: Screen size changed " +
107+
"(foldable device fold/unfold detected) - " +
108+
"width: ${newConfig.screenWidthDp}dp, height: ${newConfig.screenHeightDp}dp",
109+
)
110+
onScreenSizeChanged(current!!)
111+
}
112+
lastScreenWidthDp = newConfig.screenWidthDp
113+
lastScreenHeightDp = newConfig.screenHeightDp
114+
}
115+
116+
private fun hasScreenSizeChanged(newConfig: Configuration): Boolean {
117+
if (lastScreenWidthDp == 0 && lastScreenHeightDp == 0) {
118+
return false
119+
}
120+
return newConfig.screenWidthDp != lastScreenWidthDp ||
121+
newConfig.screenHeightDp != lastScreenHeightDp
96122
}
97123

98124
override fun onLowMemory() {}
@@ -368,6 +394,23 @@ class ApplicationService() : IApplicationService, ActivityLifecycleCallbacks, On
368394
handleFocus()
369395
}
370396

397+
/**
398+
* Handles screen size changes that occur on foldable devices when folding/unfolding.
399+
* Unlike orientation changes, foldable devices can change screen dimensions significantly
400+
* without changing orientation (e.g., Samsung Galaxy Fold going from cover screen to main screen).
401+
* This triggers the same view recreation flow as orientation changes to ensure IAMs are
402+
* properly resized and repositioned.
403+
*/
404+
private fun onScreenSizeChanged(activity: Activity) {
405+
// Remove view
406+
activityLifecycleNotifier.fire { it.onActivityStopped(activity) }
407+
408+
// Show view with new dimensions
409+
activityLifecycleNotifier.fire { it.onActivityAvailable(activity) }
410+
411+
activity.window.decorView.viewTreeObserver.addOnGlobalLayoutListener(this)
412+
}
413+
371414
private fun handleLostFocus() {
372415
if (isInForeground) {
373416
Logging.debug("ApplicationService.handleLostFocus: application is now out of focus")

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureFlag.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ internal enum class FeatureFlag(
3434
"SDK_050800_BACKGROUND_THREADING",
3535
FeatureActivationMode.APP_STARTUP
3636
),
37+
38+
/**
39+
* Enables improved IAM display handling for foldable devices.
40+
* When enabled:
41+
* - Uses WindowMetrics API (API 30+) for accurate window dimensions
42+
* - Detects screen size changes from fold/unfold events
43+
* - Recalculates IAM dimensions when screen size changes
44+
*/
45+
SDK_050800_FOLDABLE_IAM_FIX(
46+
"SDK_050800_FOLDABLE_IAM_FIX",
47+
FeatureActivationMode.IMMEDIATE
48+
),
3749
;
3850

3951
fun isEnabledIn(enabledKeys: Set<String>): Boolean {

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureManager.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.onesignal.core.internal.features
22

3+
import com.onesignal.common.FoldableIAMFeature
34
import com.onesignal.common.modeling.ISingletonModelStoreChangeHandler
45
import com.onesignal.common.modeling.ModelChangeTags
56
import com.onesignal.common.modeling.ModelChangedArgs
@@ -113,6 +114,11 @@ internal class FeatureManager(
113114
enabled = enabled,
114115
source = "FeatureManager:${feature.activationMode}"
115116
)
117+
FeatureFlag.SDK_050800_FOLDABLE_IAM_FIX ->
118+
FoldableIAMFeature.updateEnabled(
119+
enabled = enabled,
120+
source = "FeatureManager:${feature.activationMode}"
121+
)
116122
}
117123
}
118124

OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/display/impl/WebViewManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ internal class WebViewManager(
263263
showMessageView(lastPageHeight)
264264
}
265265
} else {
266-
// Activity rotated
266+
// Activity rotated or screen size changed (e.g., foldable device fold/unfold)
267267
calculateHeightAndShowWebViewAfterNewActivity()
268268
}
269269
}

0 commit comments

Comments
 (0)