Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Fixes

- Session Replay: Fix masking of non-styled `Text` Composables ([#4361](https://github.com/getsentry/sentry-java/pull/4361))

## 8.9.0

### Features
Expand Down
8 changes: 8 additions & 0 deletions scripts/toggle-codec-logs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ case "$ACTION" in
adb shell setprop log.tag.BufferQueueProducer D
adb shell setprop log.tag.ReflectedParamUpdater D
adb shell setprop log.tag.hw-BpHwBinder D
adb shell setprop log.tag.ACodec D
Copy link
Copy Markdown
Member Author

@romtsn romtsn Apr 28, 2025

Choose a reason for hiding this comment

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

I found these when running on a pixel device, so just added them here, not related to this PR

adb shell setprop log.tag.VideoCapabilities D
adb shell setprop log.tag.OMXUtils D
adb shell setprop log.tag.OMXClient D
echo "✅ Logs ENABLED"
;;
disable)
Expand All @@ -67,6 +71,10 @@ case "$ACTION" in
adb shell setprop log.tag.BufferQueueProducer SILENT
adb shell setprop log.tag.ReflectedParamUpdater SILENT
adb shell setprop log.tag.hw-BpHwBinder SILENT
adb shell setprop log.tag.ACodec SILENT
adb shell setprop log.tag.VideoCapabilities SILENT
adb shell setprop log.tag.OMXUtils SILENT
adb shell setprop log.tag.OMXClient SILENT
echo "🚫 Logs DISABLED"
;;
*)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.unit.TextUnit
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.SentryReplayOptions
Expand Down Expand Up @@ -100,14 +101,19 @@ internal object ComposeViewHierarchyNode {
?.invoke(textLayoutResults)

val (color, hasFillModifier) = node.findTextAttributes()
var textColor = textLayoutResults.firstOrNull()?.layoutInput?.style?.color
val textLayoutResult = textLayoutResults.firstOrNull()
var textColor = textLayoutResult?.layoutInput?.style?.color
if (textColor?.isUnspecified == true) {
textColor = color
}
// TODO: support multiple text layouts
val isLaidOut = textLayoutResult?.layoutInput?.style?.fontSize != TextUnit.Unspecified
// TODO: support editable text (currently there's a way to get @Composable's padding only via reflection, and we can't reliably mask input fields based on TextLayout, so we mask the whole view instead)
TextViewHierarchyNode(
layout = if (textLayoutResults.isNotEmpty() && !isEditable) ComposeTextLayout(textLayoutResults.first(), hasFillModifier) else null,
layout = if (textLayoutResult != null && !isEditable && isLaidOut) {
ComposeTextLayout(textLayoutResult, hasFillModifier)
} else {
null
},
dominantColor = textColor?.toArgb()?.toOpaque(),
x = visibleRect.left.toFloat(),
y = visibleRect.top.toFloat(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.invisibleToUser
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.test.ext.junit.runners.AndroidJUnit4
import coil.compose.AsyncImage
import io.sentry.SentryOptions
Expand All @@ -41,6 +43,7 @@ import java.io.File
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue

@RunWith(AndroidJUnit4::class)
Expand All @@ -53,6 +56,7 @@ class ComposeMaskingOptionsTest {
System.setProperty("robolectric.pixelCopyRenderMode", "hardware")
ComposeMaskingOptionsActivity.textModifierApplier = null
ComposeMaskingOptionsActivity.containerModifierApplier = null
ComposeMaskingOptionsActivity.fontSizeApplier = null
}

@Test
Expand All @@ -67,8 +71,23 @@ class ComposeMaskingOptionsTest {
val textNodes = activity.get().collectNodesOfType<TextViewHierarchyNode>(options)
assertEquals(4, textNodes.size) // [TextField, Text, Button, Activity Title]
assertTrue(textNodes.all { it.shouldMask })
// just a sanity check for parsing the tree
assertEquals("Random repo", (textNodes[1].layout as ComposeTextLayout).layout.layoutInput.text.text)
// no fontSize specified - we don't use the text layout
assertNull(textNodes.first().layout)
}

@Test
fun `when text is laid out nodes use it`() {
ComposeMaskingOptionsActivity.fontSizeApplier = { 20.sp }
val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
shadowOf(Looper.getMainLooper()).idle()

val options = SentryOptions().apply {
sessionReplay.maskAllText = true
}

val textNodes = activity.get().collectNodesOfType<TextViewHierarchyNode>(options)
// the text should be laid out when fontSize is specified
assertEquals("Random repo", (textNodes.first().layout as? ComposeTextLayout)?.layout?.layoutInput?.text?.text)
}

@Test
Expand Down Expand Up @@ -213,6 +232,7 @@ private class ComposeMaskingOptionsActivity : ComponentActivity() {
companion object {
var textModifierApplier: (() -> Modifier)? = null
var containerModifierApplier: (() -> Modifier)? = null
var fontSizeApplier: (() -> TextUnit)? = null
}

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -232,11 +252,11 @@ private class ComposeMaskingOptionsActivity : ComponentActivity() {
contentDescription = null,
modifier = Modifier.padding(vertical = 16.dp)
)
Text("Random repo", fontSize = fontSizeApplier?.invoke() ?: TextUnit.Unspecified)
TextField(
value = TextFieldValue("Placeholder"),
onValueChange = { _ -> }
)
Text("Random repo")
Button(
onClick = {},
modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ fun Github(
val scope = rememberCoroutineScope()

LaunchedEffect(perPage) {
result = GithubAPI.service.listReposAsync(user.text, perPage).random().full_name
result = try {
GithubAPI.service.listReposAsync(user.text, perPage).random().full_name
} catch (e: Throwable) {
"error"
}
}

SentryTraced("github-$user") {
Expand All @@ -133,12 +137,15 @@ fun Github(
user = newText
}
)
Text("Random repo $result")
Text("Random repo: $result")
Button(
onClick = {
scope.launch {
result =
result = try {
GithubAPI.service.listReposAsync(user.text, perPage).random().full_name
} catch (e: Throwable) {
"error"
}
}
},
modifier = Modifier
Expand Down