fix(android): preserve UTF-16 boundaries in notification text (#102442)

* fix(android): keep notification text UTF-16 safe

* test(android): cover UTF-16 notification boundaries

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
This commit is contained in:
Ben.Li 2026-07-09 15:11:32 +08:00 committed by GitHub
parent 363cf0461f
commit 3e787f3187
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 24 additions and 1 deletions

View file

@ -27,7 +27,18 @@ private const val NOTIFICATIONS_CHANGED_EVENT = "notifications.changed"
internal fun sanitizeNotificationText(value: CharSequence?): String? {
val normalized = value?.toString()?.trim().orEmpty()
// Notification extras can include long previews; cap before sending over node events.
return normalized.take(MAX_NOTIFICATION_TEXT_CHARS).ifEmpty { null }
return normalized.takeUtf16Safe(MAX_NOTIFICATION_TEXT_CHARS).ifEmpty { null }
}
private fun String.takeUtf16Safe(maxChars: Int): String {
if (length <= maxChars) return this
val end =
if (maxChars > 0 && Character.isHighSurrogate(this[maxChars - 1])) {
maxChars - 1
} else {
maxChars
}
return take(end)
}
/**

View file

@ -265,6 +265,18 @@ class NotificationsHandlerTest {
assertTrue((sanitized ?: "").all { it == 'x' })
}
@Test
fun sanitizeNotificationTextPreservesUtf16BoundariesAtLimit() {
val splitPairPrefix = "a".repeat(511)
assertEquals(splitPairPrefix, sanitizeNotificationText("$splitPairPrefix🚀 trailing text"))
val completePairPrefix = "a".repeat(510)
assertEquals(
"$completePairPrefix🚀",
sanitizeNotificationText("$completePairPrefix🚀 trailing text"),
)
}
@Test
fun notificationsActionClearablePolicy_onlyRequiresClearableForDismiss() {
assertTrue(actionRequiresClearableNotification(NotificationActionKind.Dismiss))