Skip to content
Merged
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 @@ -7,6 +7,10 @@
- Added `Environment` to the `Scope`, allowing you to overwrite the `Environment` set on the options ([#5772](https://github.com/getsentry/sentry-java/pull/5772))
- The scope `Environment` is also applied to the trace envelope header (DSC), logs, metrics and profiles, so all telemetry of a trace reports the same environment

### Improvements

- Speed up Session Replay and screenshot masking for large `maskViewClasses`/`unmaskViewClasses` lists by using constant-time class lookups ([#6410](https://github.com/getsentry/sentry-java/pull/6410))

### Fixes

- Keep UI profiling running when `startProfiler` is called before a requested stop takes effect at the end of the current chunk ([#6291](https://github.com/getsentry/sentry-java/pull/6291))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.SECONDS
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
Expand Down Expand Up @@ -696,8 +695,8 @@ class SessionCaptureStrategyTest {
assertEquals(true, optionsEvent[0].optionsPayload["maskAllText"])
assertEquals(false, optionsEvent[0].optionsPayload["maskAllImages"])
assertEquals("high", optionsEvent[0].optionsPayload["quality"])
assertContentEquals(
listOf(
assertThat(optionsEvent[0].optionsPayload["maskedViewClasses"] as Collection<*>)
.containsExactly(
"android.widget.TextView",
"android.webkit.WebView",
"android.widget.VideoView",
Expand All @@ -706,13 +705,9 @@ class SessionCaptureStrategyTest {
"com.google.android.exoplayer2.ui.PlayerView",
"com.google.android.exoplayer2.ui.StyledPlayerView",
"my.custom.View",
),
optionsEvent[0].optionsPayload["maskedViewClasses"] as Collection<*>,
)
assertContentEquals(
listOf("android.widget.ImageView"),
optionsEvent[0].optionsPayload["unmaskedViewClasses"] as Collection<*>,
)
)
assertThat(optionsEvent[0].optionsPayload["unmaskedViewClasses"] as Collection<*>)
.containsExactly("android.widget.ImageView")
},
)
}
Expand Down
15 changes: 12 additions & 3 deletions sentry/src/main/java/io/sentry/SentryMaskingOptions.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package io.sentry;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -32,7 +33,7 @@ public abstract class SentryMaskingOptions {
*
* <p>Default is empty.
*/
protected Set<String> maskViewClasses = new CopyOnWriteArraySet<>();
protected Set<String> maskViewClasses = Collections.newSetFromMap(new ConcurrentHashMap<>());

/**
* Ignore all views with the specified class names from masking. The class name is the fully
Expand All @@ -44,7 +45,7 @@ public abstract class SentryMaskingOptions {
*
* <p>Default is empty.
*/
protected Set<String> unmaskViewClasses = new CopyOnWriteArraySet<>();
protected Set<String> unmaskViewClasses = Collections.newSetFromMap(new ConcurrentHashMap<>());

/** The class name of the view container that masks all of its children. */
protected @Nullable String maskViewContainerClass = null;
Expand Down Expand Up @@ -92,6 +93,10 @@ public Set<String> getMaskViewClasses() {
}

public void addMaskViewClass(final @NotNull String className) {
// Hybrid SDKs may pass null despite the annotation, and the backing set rejects null keys
if (className == null) {
return;
}
this.maskViewClasses.add(className);
this.unmaskViewClasses.remove(className);
}
Expand All @@ -102,6 +107,10 @@ public Set<String> getUnmaskViewClasses() {
}

public void addUnmaskViewClass(final @NotNull String className) {
// Hybrid SDKs may pass null despite the annotation, and the backing set rejects null keys
if (className == null) {
return;
}
this.unmaskViewClasses.add(className);
this.maskViewClasses.remove(className);
}
Expand Down
20 changes: 20 additions & 0 deletions sentry/src/test/java/io/sentry/SentryMaskingOptionsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ class SentryMaskingOptionsTest {
assertTrue(options.unmaskViewClasses.contains("com.example.MyView"))
}

@Test
fun `addMaskViewClass ignores null`() {
val options = TestMaskingOptions()
SentryMaskingOptions::class
.java
.getMethod("addMaskViewClass", String::class.java)
.invoke(options, null)
assertTrue(options.maskViewClasses.isEmpty())
}

@Test
fun `addUnmaskViewClass ignores null`() {
val options = TestMaskingOptions()
SentryMaskingOptions::class
.java
.getMethod("addUnmaskViewClass", String::class.java)
.invoke(options, null)
assertTrue(options.unmaskViewClasses.isEmpty())
}

@Test
fun `setMaskAllText true adds TextView to maskViewClasses`() {
val options = TestMaskingOptions()
Expand Down
Loading