Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Fixes

- Session Replay: Fix Compose text masking mismatch with weighted text ([#5218](https://github.com/getsentry/sentry-java/pull/5218))

### Features

- Android: Add `beforeErrorSampling` callback to Session Replay ([#5214](https://github.com/getsentry/sentry-java/pull/5214))
Expand Down
7 changes: 6 additions & 1 deletion sentry-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion
plugins {
id("com.android.library")
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
jacoco
alias(libs.plugins.jacoco.android)
alias(libs.plugins.errorprone)
Expand Down Expand Up @@ -108,7 +109,11 @@ dependencies {
testImplementation(projects.sentryCompose)
testImplementation(projects.sentryAndroidNdk)
testImplementation(libs.dropbox.differ)
testRuntimeOnly(libs.androidx.compose.ui)
testImplementation(libs.androidx.activity.compose)
testImplementation(libs.androidx.compose.ui)
testImplementation(libs.androidx.compose.foundation)
testImplementation(libs.androidx.compose.foundation.layout)
testImplementation(libs.androidx.compose.material3)
testRuntimeOnly(libs.androidx.fragment.ktx)
testRuntimeOnly(libs.timber)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,27 @@ import android.graphics.Color
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Looper
import android.text.TextUtils
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.LinearLayout.LayoutParams
import android.widget.RadioButton
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.dropbox.differ.Color as DifferColor
import com.dropbox.differ.Image
Expand Down Expand Up @@ -410,6 +425,48 @@ class ScreenshotEventProcessorTest {
assertNotNull(bytes)
}

@Test
fun `snapshot - screenshot with ellipsized text no masking`() {
fixture.activity = buildActivity(EllipsizedTextActivity::class.java, null).setup().get()
val bytes =
processEventForSnapshots(
"screenshot_mask_ellipsized_view_unmasked",
isReplayAvailable = false,
)
assertNotNull(bytes)
}

@Test
fun `snapshot - screenshot with ellipsized text masking`() {
fixture.activity = buildActivity(EllipsizedTextActivity::class.java, null).setup().get()
val bytes =
processEventForSnapshots("screenshot_mask_ellipsized_view_masked") {
it.screenshot.setMaskAllText(true)
}
assertNotNull(bytes)
}

@Test
fun `snapshot - compose text no masking`() {
fixture.activity = buildActivity(ComposeTextActivity::class.java, null).setup().get()
val bytes =
processEventForSnapshots(
"screenshot_mask_ellipsized_compose_unmasked",
isReplayAvailable = false,
)
assertNotNull(bytes)
}

@Test
fun `snapshot - compose text with masking`() {
fixture.activity = buildActivity(ComposeTextActivity::class.java, null).setup().get()
val bytes =
processEventForSnapshots("screenshot_mask_ellipsized_compose_masked") {
it.screenshot.setMaskAllText(true)
}
assertNotNull(bytes)
}

// endregion

private fun getEvent(): SentryEvent = SentryEvent(Throwable("Throwable"))
Expand Down Expand Up @@ -484,6 +541,189 @@ private class CustomView(context: Context) : View(context) {
}
}

private class EllipsizedTextActivity : Activity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val longText = "This is a very long text that should be ellipsized when it does not fit"

val linearLayout =
LinearLayout(this).apply {
setBackgroundColor(Color.WHITE)
orientation = LinearLayout.VERTICAL
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
setPadding(10, 10, 10, 10)
}

// Ellipsize end
linearLayout.addView(
TextView(this).apply {
text = longText
setTextColor(Color.BLACK)
textSize = 16f
maxLines = 1
ellipsize = TextUtils.TruncateAt.END
setBackgroundColor(Color.LTGRAY)
layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
setMargins(0, 8, 0, 0)
}
}
)

// Ellipsize middle
linearLayout.addView(
TextView(this).apply {
text = longText
setTextColor(Color.BLACK)
textSize = 16f
maxLines = 1
ellipsize = TextUtils.TruncateAt.MIDDLE
setBackgroundColor(Color.LTGRAY)
layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
setMargins(0, 8, 0, 0)
}
}
)

// Ellipsize start
linearLayout.addView(
TextView(this).apply {
text = longText
setTextColor(Color.BLACK)
textSize = 16f
maxLines = 1
ellipsize = TextUtils.TruncateAt.START
setBackgroundColor(Color.LTGRAY)
layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
setMargins(0, 8, 0, 0)
}
}
)

// Non-ellipsized text for comparison
linearLayout.addView(
TextView(this).apply {
text = "Short text"
setTextColor(Color.BLACK)
textSize = 16f
setBackgroundColor(Color.LTGRAY)
layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
setMargins(0, 8, 0, 0)
}
}
)

setContentView(linearLayout)
}
}

private class ComposeTextActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val longText = "This is a very long text that should be ellipsized when it does not fit in view"

setContent {
Column(
modifier =
Modifier.fillMaxWidth()
.background(androidx.compose.ui.graphics.Color.White)
.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
// Ellipsis overflow
Text(
longText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontSize = 16.sp,
modifier =
Modifier.fillMaxWidth().background(androidx.compose.ui.graphics.Color.LightGray),
)

// Text with textAlign center
Text(
"Centered text",
textAlign = TextAlign.Center,
fontSize = 16.sp,
modifier =
Modifier.fillMaxWidth().background(androidx.compose.ui.graphics.Color.LightGray),
)

// Text with textAlign end
Text(
"End-aligned text",
textAlign = TextAlign.End,
fontSize = 16.sp,
modifier =
Modifier.fillMaxWidth().background(androidx.compose.ui.graphics.Color.LightGray),
)

// Ellipsis with textAlign center
Text(
longText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
fontSize = 16.sp,
modifier =
Modifier.fillMaxWidth().background(androidx.compose.ui.graphics.Color.LightGray),
)

// Weighted row with text
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
"Weight 1",
fontSize = 16.sp,
modifier = Modifier.weight(1f).background(androidx.compose.ui.graphics.Color.LightGray),
)
Text(
"Weight 1",
fontSize = 16.sp,
modifier = Modifier.weight(1f).background(androidx.compose.ui.graphics.Color.LightGray),
)
}

// Weighted row with ellipsized text
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
longText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontSize = 16.sp,
modifier = Modifier.weight(1f).background(androidx.compose.ui.graphics.Color.LightGray),
)
Text(
longText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.End,
fontSize = 16.sp,
modifier = Modifier.weight(1f).background(androidx.compose.ui.graphics.Color.LightGray),
)
}

// Short text (for comparison)
Text(
"Short text",
fontSize = 16.sp,
modifier = Modifier.background(androidx.compose.ui.graphics.Color.LightGray),
)
}
}
}
}

private class MaskingActivity : Activity() {

override fun onCreate(savedInstanceState: Bundle?) {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading