diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc4c2d1009c..ce2bd2cf4e0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,15 +6,16 @@
- Add support for `feedback` envelope header item type ([#3687](https://github.com/getsentry/sentry-java/pull/3687))
- Add breadcrumb.origin field ([#3727](https://github.com/getsentry/sentry-java/pull/3727))
-- Session Replay: Add options to selectively redact/ignore views from being captured. The following options are available: ([#3689](https://github.com/getsentry/sentry-java/pull/3689))
- - `android:tag="sentry-redact|sentry-ignore"` in XML or `view.setTag("sentry-redact|sentry-ignore")` in code tags
- - if you already have a tag set for a view, you can set a tag by id: `` in XML or `view.setTag(io.sentry.android.replay.R.id.sentry_privacy, "redact|ignore")` in code
- - `view.sentryReplayRedact()` or `view.sentryReplayIgnore()` extension functions
- - redact/ignore `View`s of a certain type by adding fully-qualified classname to one of the lists `options.experimental.sessionReplay.addRedactViewClass()` or `options.experimental.sessionReplay.addIgnoreViewClass()`. Note, that all of the view subclasses/subtypes will be redacted/ignored as well
- - For example, (this is already a default behavior) to redact all `TextView`s and their subclasses (`RadioButton`, `EditText`, etc.): `options.experimental.sessionReplay.addRedactViewClass("android.widget.TextView")`
+- Session Replay: Add options to selectively mask/unmask views captured in replay. The following options are available: ([#3689](https://github.com/getsentry/sentry-java/pull/3689))
+ - `android:tag="sentry-mask|sentry-unmask"` in XML or `view.setTag("sentry-mask|sentry-unmask")` in code tags
+ - if you already have a tag set for a view, you can set a tag by id: `` in XML or `view.setTag(io.sentry.android.replay.R.id.sentry_privacy, "mask|unmask")` in code
+ - `view.sentryReplayMask()` or `view.sentryReplayUnmask()` extension functions
+ - mask/unmask `View`s of a certain type by adding fully-qualified classname to one of the lists `options.experimental.sessionReplay.addMaskViewClass()` or `options.experimental.sessionReplay.addUnmaskViewClass()`. Note, that all of the view subclasses/subtypes will be masked/unmasked as well
+ - For example, (this is already a default behavior) to mask all `TextView`s and their subclasses (`RadioButton`, `EditText`, etc.): `options.experimental.sessionReplay.addMaskViewClass("android.widget.TextView")`
- If you're using code obfuscation, adjust your proguard-rules accordingly, so your custom view class name is not minified
- Session Replay: Support Jetpack Compose masking ([#3739](https://github.com/getsentry/sentry-java/pull/3739))
- - To selectively mask/unmask @Composables, use `Modifier.sentryReplayRedact()` and `Modifier.sentryReplayIgnore()` modifiers
+ - To selectively mask/unmask @Composables, use `Modifier.sentryReplayMask()` and `Modifier.sentryReplayUnmask()` modifiers
+- Session Replay: Mask `WebView`, `VideoView` and `androidx.media3.ui.PlayerView` by default ([#3775](https://github.com/getsentry/sentry-java/pull/3775))
### Fixes
@@ -29,6 +30,7 @@
- `options.experimental.sessionReplay.errorSampleRate` was renamed to `options.experimental.sessionReplay.onErrorSampleRate` ([#3637](https://github.com/getsentry/sentry-java/pull/3637))
- Manifest option `io.sentry.session-replay.error-sample-rate` was renamed to `io.sentry.session-replay.on-error-sample-rate` ([#3637](https://github.com/getsentry/sentry-java/pull/3637))
+- Change `redactAllText` and `redactAllImages` to `maskAllText` and `maskAllImages` ([#3741](https://github.com/getsentry/sentry-java/pull/3741))
## 7.14.0
diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java
index fc66c9d6eea..96d54d98de6 100644
--- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java
+++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java
@@ -108,9 +108,9 @@ final class ManifestMetadataReader {
static final String REPLAYS_ERROR_SAMPLE_RATE = "io.sentry.session-replay.on-error-sample-rate";
- static final String REPLAYS_REDACT_ALL_TEXT = "io.sentry.session-replay.redact-all-text";
+ static final String REPLAYS_MASK_ALL_TEXT = "io.sentry.session-replay.mask-all-text";
- static final String REPLAYS_REDACT_ALL_IMAGES = "io.sentry.session-replay.redact-all-images";
+ static final String REPLAYS_MASK_ALL_IMAGES = "io.sentry.session-replay.mask-all-images";
/** ManifestMetadataReader ctor */
private ManifestMetadataReader() {}
@@ -409,12 +409,12 @@ static void applyMetadata(
options
.getExperimental()
.getSessionReplay()
- .setRedactAllText(readBool(metadata, logger, REPLAYS_REDACT_ALL_TEXT, true));
+ .setMaskAllText(readBool(metadata, logger, REPLAYS_MASK_ALL_TEXT, true));
options
.getExperimental()
.getSessionReplay()
- .setRedactAllImages(readBool(metadata, logger, REPLAYS_REDACT_ALL_IMAGES, true));
+ .setMaskAllImages(readBool(metadata, logger, REPLAYS_MASK_ALL_IMAGES, true));
}
options
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt
index 8a86fcb2c57..e068af7b1ce 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt
@@ -1465,21 +1465,21 @@ class ManifestMetadataReaderTest {
}
@Test
- fun `applyMetadata reads session replay redact flags to options`() {
+ fun `applyMetadata reads session replay mask flags to options`() {
// Arrange
- val bundle = bundleOf(ManifestMetadataReader.REPLAYS_REDACT_ALL_TEXT to false, ManifestMetadataReader.REPLAYS_REDACT_ALL_IMAGES to false)
+ val bundle = bundleOf(ManifestMetadataReader.REPLAYS_MASK_ALL_TEXT to false, ManifestMetadataReader.REPLAYS_MASK_ALL_IMAGES to false)
val context = fixture.getContext(metaData = bundle)
// Act
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
// Assert
- assertTrue(fixture.options.experimental.sessionReplay.ignoreViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
- assertTrue(fixture.options.experimental.sessionReplay.ignoreViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
+ assertTrue(fixture.options.experimental.sessionReplay.unmaskViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
+ assertTrue(fixture.options.experimental.sessionReplay.unmaskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
}
@Test
- fun `applyMetadata reads session replay redact flags to options and keeps default if not found`() {
+ fun `applyMetadata reads session replay mask flags to options and keeps default if not found`() {
// Arrange
val context = fixture.getContext()
@@ -1487,7 +1487,7 @@ class ManifestMetadataReaderTest {
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
// Assert
- assertTrue(fixture.options.experimental.sessionReplay.redactViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
- assertTrue(fixture.options.experimental.sessionReplay.redactViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
+ assertTrue(fixture.options.experimental.sessionReplay.maskViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
+ assertTrue(fixture.options.experimental.sessionReplay.maskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
}
}
diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt
index 6ba69ffdcbe..d75e0f88a24 100644
--- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt
+++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt
@@ -440,7 +440,7 @@ class SentryAndroidTest {
.untilTrue(asserted)
// assert that persisted values have changed
- options.executorService.close(5000L) // finalizes all enqueued persisting tasks
+ options.executorService.close(10000L) // finalizes all enqueued persisting tasks
assertEquals(
"TestActivity",
PersistingScopeObserver.read(options, TRANSACTION_FILENAME, String::class.java)
diff --git a/sentry-android-replay/api/sentry-android-replay.api b/sentry-android-replay/api/sentry-android-replay.api
index 4b4c59b9a2a..a08fb1dd988 100644
--- a/sentry-android-replay/api/sentry-android-replay.api
+++ b/sentry-android-replay/api/sentry-android-replay.api
@@ -29,8 +29,8 @@ public final class io/sentry/android/replay/GeneratedVideo {
}
public final class io/sentry/android/replay/ModifierExtensionsKt {
- public static final fun sentryReplayIgnore (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
- public static final fun sentryReplayRedact (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+ public static final fun sentryReplayMask (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
+ public static final fun sentryReplayUnmask (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
}
public abstract interface class io/sentry/android/replay/Recorder : java/io/Closeable {
@@ -120,15 +120,15 @@ public final class io/sentry/android/replay/SentryReplayModifiers {
}
public final class io/sentry/android/replay/SessionReplayOptionsKt {
- public static final fun getRedactAllImages (Lio/sentry/SentryReplayOptions;)Z
- public static final fun getRedactAllText (Lio/sentry/SentryReplayOptions;)Z
- public static final fun setRedactAllImages (Lio/sentry/SentryReplayOptions;Z)V
- public static final fun setRedactAllText (Lio/sentry/SentryReplayOptions;Z)V
+ public static final fun getMaskAllImages (Lio/sentry/SentryReplayOptions;)Z
+ public static final fun getMaskAllText (Lio/sentry/SentryReplayOptions;)Z
+ public static final fun setMaskAllImages (Lio/sentry/SentryReplayOptions;Z)V
+ public static final fun setMaskAllText (Lio/sentry/SentryReplayOptions;Z)V
}
public final class io/sentry/android/replay/ViewExtensionsKt {
- public static final fun sentryReplayIgnore (Landroid/view/View;)V
- public static final fun sentryReplayRedact (Landroid/view/View;)V
+ public static final fun sentryReplayMask (Landroid/view/View;)V
+ public static final fun sentryReplayUnmask (Landroid/view/View;)V
}
public final class io/sentry/android/replay/gestures/GestureRecorder : io/sentry/android/replay/OnRootViewsChangedListener {
@@ -230,7 +230,7 @@ public abstract class io/sentry/android/replay/viewhierarchy/ViewHierarchyNode {
public final fun getElevation ()F
public final fun getHeight ()I
public final fun getParent ()Lio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;
- public final fun getShouldRedact ()Z
+ public final fun getShouldMask ()Z
public final fun getVisibleRect ()Landroid/graphics/Rect;
public final fun getWidth ()I
public final fun getX ()F
diff --git a/sentry-android-replay/proguard-rules.pro b/sentry-android-replay/proguard-rules.pro
index 445c89b526b..378c0964f8c 100644
--- a/sentry-android-replay/proguard-rules.pro
+++ b/sentry-android-replay/proguard-rules.pro
@@ -2,13 +2,13 @@
# debugging stack traces.
-keepattributes SourceFile,LineNumberTable
-# Rules to detect Images/Icons and redact them
+# Rules to detect Images/Icons and mask them
-dontwarn androidx.compose.ui.graphics.painter.Painter
-keepnames class * extends androidx.compose.ui.graphics.painter.Painter
-keepclasseswithmembernames class * {
androidx.compose.ui.graphics.painter.Painter painter;
}
-# Rules to detect Text colors and if they have Modifier.fillMaxWidth to later redact them
+# Rules to detect Text colors and if they have Modifier.fillMaxWidth to later mask them
-dontwarn androidx.compose.ui.graphics.ColorProducer
-dontwarn androidx.compose.foundation.layout.FillElement
-keepnames class androidx.compose.foundation.layout.FillElement
@@ -18,3 +18,11 @@
# Rules to detect a compose view to parse its hierarchy
-dontwarn androidx.compose.ui.platform.AndroidComposeView
-keepnames class androidx.compose.ui.platform.AndroidComposeView
+# Rules to detect a media player view to later mask it
+-dontwarn androidx.media3.ui.PlayerView
+-keepnames class androidx.media3.ui.PlayerView
+# Rules to detect a ExoPlayer view to later mask it
+-dontwarn com.google.android.exoplayer2.ui.PlayerView
+-keepnames class com.google.android.exoplayer2.ui.PlayerView
+-dontwarn com.google.android.exoplayer2.ui.StyledPlayerView
+-keepnames class com.google.android.exoplayer2.ui.StyledPlayerView
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ModifierExtensions.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ModifierExtensions.kt
index b1b119a89cc..b5d52223886 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ModifierExtensions.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ModifierExtensions.kt
@@ -12,18 +12,18 @@ public object SentryReplayModifiers {
)
}
-public fun Modifier.sentryReplayRedact(): Modifier {
+public fun Modifier.sentryReplayMask(): Modifier {
return semantics(
properties = {
- this[SentryPrivacy] = "redact"
+ this[SentryPrivacy] = "mask"
}
)
}
-public fun Modifier.sentryReplayIgnore(): Modifier {
+public fun Modifier.sentryReplayUnmask(): Modifier {
return semantics(
properties = {
- this[SentryPrivacy] = "ignore"
+ this[SentryPrivacy] = "unmask"
}
)
}
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt
index 5b779babe03..8f823fa17c2 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt
@@ -124,11 +124,11 @@ internal class ScreenshotRecorder(
val viewHierarchy = ViewHierarchyNode.fromView(root, null, 0, options)
root.traverse(viewHierarchy, options)
- recorder.submitSafely(options, "screenshot_recorder.redact") {
+ recorder.submitSafely(options, "screenshot_recorder.mask") {
val canvas = Canvas(bitmap)
canvas.setMatrix(prescaledMatrix)
viewHierarchy.traverse { node ->
- if (node.shouldRedact && (node.width > 0 && node.height > 0)) {
+ if (node.shouldMask && (node.width > 0 && node.height > 0)) {
node.visibleRect ?: return@traverse false
// TODO: investigate why it returns true on RN when it shouldn't
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/SessionReplayOptions.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/SessionReplayOptions.kt
index e3e6605a968..fb5105565b6 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/SessionReplayOptions.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/SessionReplayOptions.kt
@@ -2,30 +2,30 @@ package io.sentry.android.replay
import io.sentry.SentryReplayOptions
-// since we don't have getters for redactAllText and redactAllImages, they won't be accessible as
+// since we don't have getters for maskAllText and maskAllimages, they won't be accessible as
// properties in Kotlin, therefore we create these extensions where a getter is dummy, but a setter
// delegates to the corresponding method in SentryReplayOptions
/**
- * Redact all text content. Draws a rectangle of text bounds with text color on top. By default
- * only views extending TextView are redacted.
+ * Mask all text content. Draws a rectangle of text bounds with text color on top. By default
+ * only views extending TextView are masked.
*
*
Default is enabled.
*/
-var SentryReplayOptions.redactAllText: Boolean
+var SentryReplayOptions.maskAllText: Boolean
@Deprecated("Getter is unsupported.", level = DeprecationLevel.ERROR)
get() = error("Getter not supported")
- set(value) = setRedactAllText(value)
+ set(value) = setMaskAllText(value)
/**
- * Redact all image content. Draws a rectangle of image bounds with image's dominant color on top.
+ * Mask all image content. Draws a rectangle of image bounds with image's dominant color on top.
* By default only views extending ImageView with BitmapDrawable or custom Drawable type are
- * redacted. ColorDrawable, InsetDrawable, VectorDrawable are all considered non-PII, as they come
+ * masked. ColorDrawable, InsetDrawable, VectorDrawable are all considered non-PII, as they come
* from the apk.
*
*
Default is enabled.
*/
-var SentryReplayOptions.redactAllImages: Boolean
+var SentryReplayOptions.maskAllImages: Boolean
@Deprecated("Getter is unsupported.", level = DeprecationLevel.ERROR)
get() = error("Getter not supported")
- set(value) = setRedactAllImages(value)
+ set(value) = setMaskAllImages(value)
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ViewExtensions.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ViewExtensions.kt
index 37061a5b77c..2625399c99a 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ViewExtensions.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ViewExtensions.kt
@@ -3,16 +3,16 @@ package io.sentry.android.replay
import android.view.View
/**
- * Marks this view to be redacted in session replay.
+ * Marks this view to be masked in session replay.
*/
-fun View.sentryReplayRedact() {
- setTag(R.id.sentry_privacy, "redact")
+fun View.sentryReplayMask() {
+ setTag(R.id.sentry_privacy, "mask")
}
/**
- * Marks this view to be ignored from redaction in session.
+ * Marks this view to be unmasked in session replay.
* All its content will be visible in the replay, use with caution.
*/
-fun View.sentryReplayIgnore() {
- setTag(R.id.sentry_privacy, "ignore")
+fun View.sentryReplayUnmask() {
+ setTag(R.id.sentry_privacy, "unmask")
}
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Nodes.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Nodes.kt
index 12152f50cb7..56083717221 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Nodes.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Nodes.kt
@@ -37,7 +37,7 @@ internal class ComposeTextLayout(internal val layout: TextLayoutResult, private
// TODO: probably most of the below we can do via bytecode instrumentation and speed up at runtime
/**
- * This method is necessary to redact images in Compose.
+ * This method is necessary to mask images in Compose.
*
* We heuristically look up for classes that have a [Painter] modifier, usually they all have a
* `Painter` string in their name, e.g. PainterElement, PainterModifierNodeElement or
@@ -71,9 +71,9 @@ internal fun LayoutNode.findPainter(): Painter? {
* [androidx.compose.ui.graphics.painter.BrushPainter]
*
* In theory, [androidx.compose.ui.graphics.painter.BitmapPainter] can also come from local assets,
- * but it can as well come from a network resource, so we preemptively redact it.
+ * but it can as well come from a network resource, so we preemptively mask it.
*/
-internal fun Painter.isRedactable(): Boolean {
+internal fun Painter.isMaskable(): Boolean {
val className = this::class.java.name
return !className.contains("Vector") &&
!className.contains("Color") &&
@@ -83,11 +83,11 @@ internal fun Painter.isRedactable(): Boolean {
internal data class TextAttributes(val color: Color?, val hasFillModifier: Boolean)
/**
- * This method is necessary to redact text in Compose.
+ * This method is necessary to mask text in Compose.
*
* We heuristically look up for classes that have a [Text] modifier, usually they all have a
* `Text` string in their name, e.g. TextStringSimpleElement or TextAnnotatedStringElement. We then
- * get the color from the modifier, to be able to redact it with the correct color.
+ * get the color from the modifier, to be able to mask it with the correct color.
*
* We also look up for classes that have a [Fill] modifier, usually they all have a `Fill` string in
* their name, e.g. FillElement. This is necessary to workaround a Compose bug where single-line
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Views.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Views.kt
index 1c6111c1b0f..0a0656de52e 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Views.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/util/Views.kt
@@ -88,9 +88,9 @@ internal fun View.isVisibleToUser(): Pair {
@SuppressLint("ObsoleteSdkInt")
@TargetApi(21)
-internal fun Drawable?.isRedactable(): Boolean {
+internal fun Drawable?.isMaskable(): Boolean {
// TODO: maybe find a way how to check if the drawable is coming from the apk or loaded from network
- // TODO: otherwise maybe check for the bitmap size and don't redact those that take a lot of height (e.g. a background of a whatsapp chat)
+ // TODO: otherwise maybe check for the bitmap size and don't mask those that take a lot of height (e.g. a background of a whatsapp chat)
return when (this) {
is InsetDrawable, is ColorDrawable, is VectorDrawable, is GradientDrawable -> false
is BitmapDrawable -> {
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt
index baf521a2e67..211decc098d 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt
@@ -136,7 +136,7 @@ internal class SimpleVideoEncoder(
)
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
format.setFloat(MediaFormat.KEY_FRAME_RATE, muxerConfig.frameRate.toFloat())
- format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, -1) // use -1 to force always non-key frames, meaning only partial updates to save the video size
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 6) // use 6 to force non-key frames, meaning only partial updates to save the video size. Every 6th second is a key frame, which is useful for buffer mode
format
}
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt
index c611b91b473..888528f769b 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt
@@ -23,7 +23,7 @@ import io.sentry.android.replay.util.ComposeTextLayout
import io.sentry.android.replay.util.boundsInWindow
import io.sentry.android.replay.util.findPainter
import io.sentry.android.replay.util.findTextAttributes
-import io.sentry.android.replay.util.isRedactable
+import io.sentry.android.replay.util.isMaskable
import io.sentry.android.replay.util.toOpaque
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.GenericViewHierarchyNode
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.ImageViewHierarchyNode
@@ -45,22 +45,22 @@ internal object ComposeViewHierarchyNode {
}
}
- private fun LayoutNode.shouldRedact(isImage: Boolean, options: SentryOptions): Boolean {
+ private fun LayoutNode.shouldMask(isImage: Boolean, options: SentryOptions): Boolean {
val sentryPrivacyModifier = collapsedSemantics?.getOrNull(SentryReplayModifiers.SentryPrivacy)
- if (sentryPrivacyModifier == "ignore") {
+ if (sentryPrivacyModifier == "unmask") {
return false
}
- if (sentryPrivacyModifier == "redact") {
+ if (sentryPrivacyModifier == "mask") {
return true
}
val className = getProxyClassName(isImage)
- if (options.experimental.sessionReplay.ignoreViewClasses.contains(className)) {
+ if (options.experimental.sessionReplay.unmaskViewClasses.contains(className)) {
return false
}
- return options.experimental.sessionReplay.redactViewClasses.contains(className)
+ return options.experimental.sessionReplay.maskViewClasses.contains(className)
}
private var _rootCoordinates: LayoutCoordinates? = null
@@ -90,7 +90,7 @@ internal object ComposeViewHierarchyNode {
val positionInWindow = node.coordinates.positionInWindow()
return when {
semantics?.contains(SemanticsProperties.Text) == true || isEditable -> {
- val shouldRedact = isVisible && node.shouldRedact(isImage = false, options)
+ val shouldMask = isVisible && node.shouldMask(isImage = false, options)
parent?.setImportantForCaptureToAncestors(true)
val textLayoutResults = mutableListOf()
@@ -115,7 +115,7 @@ internal object ComposeViewHierarchyNode {
elevation = (parent?.elevation ?: 0f),
distance = distance,
parent = parent,
- shouldRedact = shouldRedact,
+ shouldMask = shouldMask,
isImportantForContentCapture = true,
isVisible = isVisible,
visibleRect = visibleRect
@@ -124,7 +124,7 @@ internal object ComposeViewHierarchyNode {
else -> {
val painter = node.findPainter()
if (painter != null) {
- val shouldRedact = isVisible && node.shouldRedact(isImage = true, options)
+ val shouldMask = isVisible && node.shouldMask(isImage = true, options)
parent?.setImportantForCaptureToAncestors(true)
ImageViewHierarchyNode(
@@ -137,11 +137,11 @@ internal object ComposeViewHierarchyNode {
parent = parent,
isVisible = isVisible,
isImportantForContentCapture = true,
- shouldRedact = shouldRedact && painter.isRedactable(),
+ shouldMask = shouldMask && painter.isMaskable(),
visibleRect = visibleRect
)
} else {
- val shouldRedact = isVisible && node.shouldRedact(isImage = false, options)
+ val shouldMask = isVisible && node.shouldMask(isImage = false, options)
// TODO: this currently does not support embedded AndroidViews, we'd have to
// TODO: traverse the ViewHierarchyNode here again. For now we can recommend
@@ -154,7 +154,7 @@ internal object ComposeViewHierarchyNode {
elevation = (parent?.elevation ?: 0f),
distance = distance,
parent = parent,
- shouldRedact = shouldRedact,
+ shouldMask = shouldMask,
isImportantForContentCapture = false, /* will be set by children */
isVisible = isVisible,
visibleRect = visibleRect
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt
index a231e4f3d23..ef05ecb0296 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ViewHierarchyNode.kt
@@ -9,7 +9,7 @@ import io.sentry.SentryOptions
import io.sentry.android.replay.R
import io.sentry.android.replay.util.AndroidTextLayout
import io.sentry.android.replay.util.TextLayout
-import io.sentry.android.replay.util.isRedactable
+import io.sentry.android.replay.util.isMaskable
import io.sentry.android.replay.util.isVisibleToUser
import io.sentry.android.replay.util.toOpaque
import io.sentry.android.replay.util.totalPaddingTopSafe
@@ -25,7 +25,7 @@ sealed class ViewHierarchyNode(
/* Distance to the parent (index) */
val distance: Int,
val parent: ViewHierarchyNode? = null,
- val shouldRedact: Boolean = false,
+ val shouldMask: Boolean = false,
/* Whether the node is important for content capture (=non-empty container) */
var isImportantForContentCapture: Boolean = false,
val isVisible: Boolean = false,
@@ -41,11 +41,11 @@ sealed class ViewHierarchyNode(
elevation: Float,
distance: Int,
parent: ViewHierarchyNode? = null,
- shouldRedact: Boolean = false,
+ shouldMask: Boolean = false,
isImportantForContentCapture: Boolean = false,
isVisible: Boolean = false,
visibleRect: Rect? = null
- ) : ViewHierarchyNode(x, y, width, height, elevation, distance, parent, shouldRedact, isImportantForContentCapture, isVisible, visibleRect)
+ ) : ViewHierarchyNode(x, y, width, height, elevation, distance, parent, shouldMask, isImportantForContentCapture, isVisible, visibleRect)
class TextViewHierarchyNode(
val layout: TextLayout? = null,
@@ -59,11 +59,11 @@ sealed class ViewHierarchyNode(
elevation: Float,
distance: Int,
parent: ViewHierarchyNode? = null,
- shouldRedact: Boolean = false,
+ shouldMask: Boolean = false,
isImportantForContentCapture: Boolean = false,
isVisible: Boolean = false,
visibleRect: Rect? = null
- ) : ViewHierarchyNode(x, y, width, height, elevation, distance, parent, shouldRedact, isImportantForContentCapture, isVisible, visibleRect)
+ ) : ViewHierarchyNode(x, y, width, height, elevation, distance, parent, shouldMask, isImportantForContentCapture, isVisible, visibleRect)
class ImageViewHierarchyNode(
x: Float,
@@ -73,11 +73,11 @@ sealed class ViewHierarchyNode(
elevation: Float,
distance: Int,
parent: ViewHierarchyNode? = null,
- shouldRedact: Boolean = false,
+ shouldMask: Boolean = false,
isImportantForContentCapture: Boolean = false,
isVisible: Boolean = false,
visibleRect: Rect? = null
- ) : ViewHierarchyNode(x, y, width, height, elevation, distance, parent, shouldRedact, isImportantForContentCapture, isVisible, visibleRect)
+ ) : ViewHierarchyNode(x, y, width, height, elevation, distance, parent, shouldMask, isImportantForContentCapture, isVisible, visibleRect)
/**
* Basically replicating this: https://developer.android.com/reference/android/view/View#isImportantForContentCapture()
@@ -233,8 +233,8 @@ sealed class ViewHierarchyNode(
)
companion object {
- private const val SENTRY_IGNORE_TAG = "sentry-ignore"
- private const val SENTRY_REDACT_TAG = "sentry-redact"
+ private const val SENTRY_UNMASK_TAG = "sentry-unmask"
+ private const val SENTRY_MASK_TAG = "sentry-mask"
private fun Class<*>.isAssignableFrom(set: Set): Boolean {
var cls: Class<*>? = this
@@ -248,29 +248,29 @@ sealed class ViewHierarchyNode(
return false
}
- private fun View.shouldRedact(options: SentryOptions): Boolean {
- if ((tag as? String)?.lowercase()?.contains(SENTRY_IGNORE_TAG) == true ||
- getTag(R.id.sentry_privacy) == "ignore"
+ private fun View.shouldMask(options: SentryOptions): Boolean {
+ if ((tag as? String)?.lowercase()?.contains(SENTRY_UNMASK_TAG) == true ||
+ getTag(R.id.sentry_privacy) == "unmask"
) {
return false
}
- if ((tag as? String)?.lowercase()?.contains(SENTRY_REDACT_TAG) == true ||
- getTag(R.id.sentry_privacy) == "redact"
+ if ((tag as? String)?.lowercase()?.contains(SENTRY_MASK_TAG) == true ||
+ getTag(R.id.sentry_privacy) == "mask"
) {
return true
}
- if (this.javaClass.isAssignableFrom(options.experimental.sessionReplay.ignoreViewClasses)) {
+ if (this.javaClass.isAssignableFrom(options.experimental.sessionReplay.unmaskViewClasses)) {
return false
}
- return this.javaClass.isAssignableFrom(options.experimental.sessionReplay.redactViewClasses)
+ return this.javaClass.isAssignableFrom(options.experimental.sessionReplay.maskViewClasses)
}
fun fromView(view: View, parent: ViewHierarchyNode?, distance: Int, options: SentryOptions): ViewHierarchyNode {
val (isVisible, visibleRect) = view.isVisibleToUser()
- val shouldRedact = isVisible && view.shouldRedact(options)
+ val shouldMask = isVisible && view.shouldMask(options)
when (view) {
is TextView -> {
parent?.setImportantForCaptureToAncestors(true)
@@ -284,7 +284,7 @@ sealed class ViewHierarchyNode(
width = view.width,
height = view.height,
elevation = (parent?.elevation ?: 0f) + view.elevation,
- shouldRedact = shouldRedact,
+ shouldMask = shouldMask,
distance = distance,
parent = parent,
isImportantForContentCapture = true,
@@ -305,7 +305,7 @@ sealed class ViewHierarchyNode(
parent = parent,
isVisible = isVisible,
isImportantForContentCapture = true,
- shouldRedact = shouldRedact && view.drawable?.isRedactable() == true,
+ shouldMask = shouldMask && view.drawable?.isMaskable() == true,
visibleRect = visibleRect
)
}
@@ -319,7 +319,7 @@ sealed class ViewHierarchyNode(
(parent?.elevation ?: 0f) + view.elevation,
distance = distance,
parent = parent,
- shouldRedact = shouldRedact,
+ shouldMask = shouldMask,
isImportantForContentCapture = false, /* will be set by children */
isVisible = isVisible,
visibleRect = visibleRect
diff --git a/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/ComposeRedactionOptionsTest.kt b/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/ComposeMaskingOptionsTest.kt
similarity index 67%
rename from sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/ComposeRedactionOptionsTest.kt
rename to sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/ComposeMaskingOptionsTest.kt
index 981e3514080..e5330fa8277 100644
--- a/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/ComposeRedactionOptionsTest.kt
+++ b/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/ComposeMaskingOptionsTest.kt
@@ -22,10 +22,10 @@ import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import coil.compose.AsyncImage
import io.sentry.SentryOptions
-import io.sentry.android.replay.redactAllImages
-import io.sentry.android.replay.redactAllText
-import io.sentry.android.replay.sentryReplayIgnore
-import io.sentry.android.replay.sentryReplayRedact
+import io.sentry.android.replay.maskAllImages
+import io.sentry.android.replay.maskAllText
+import io.sentry.android.replay.sentryReplayMask
+import io.sentry.android.replay.sentryReplayUnmask
import io.sentry.android.replay.util.ComposeTextLayout
import io.sentry.android.replay.util.traverse
import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.GenericViewHierarchyNode
@@ -43,132 +43,132 @@ import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
@Config(sdk = [30])
-class ComposeRedactionOptionsTest {
+class ComposeMaskingOptionsTest {
@Before
fun setup() {
System.setProperty("robolectric.areWindowsMarkedVisible", "true")
- ComposeRedactionOptionsActivity.textModifierApplier = null
- ComposeRedactionOptionsActivity.containerModifierApplier = null
+ ComposeMaskingOptionsActivity.textModifierApplier = null
+ ComposeMaskingOptionsActivity.containerModifierApplier = null
}
@Test
- fun `when redactAllText is set all Text nodes are redacted`() {
- val activity = buildActivity(ComposeRedactionOptionsActivity::class.java).setup()
+ fun `when maskAllText is set all Text nodes are masked`() {
+ val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = true
+ experimental.sessionReplay.maskAllText = true
}
val textNodes = activity.get().collectNodesOfType(options)
assertEquals(4, textNodes.size) // [TextField, Text, Button, Activity Title]
- assertTrue(textNodes.all { it.shouldRedact })
+ 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)
}
@Test
- fun `when redactAllText is set to false all Text nodes are ignored`() {
- val activity = buildActivity(ComposeRedactionOptionsActivity::class.java).setup()
+ fun `when maskAllText is set to false all Text nodes are unmasked`() {
+ val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = false
+ experimental.sessionReplay.maskAllText = false
}
val textNodes = activity.get().collectNodesOfType(options)
assertEquals(4, textNodes.size) // [TextField, Text, Button, Activity Title]
- assertTrue(textNodes.none { it.shouldRedact })
+ assertTrue(textNodes.none { it.shouldMask })
}
@Test
- fun `when redactAllImages is set all Image nodes are redacted`() {
- val activity = buildActivity(ComposeRedactionOptionsActivity::class.java).setup()
+ fun `when maskAllImages is set all Image nodes are masked`() {
+ val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllImages = true
+ experimental.sessionReplay.maskAllImages = true
}
val imageNodes = activity.get().collectNodesOfType(options)
assertEquals(1, imageNodes.size) // [AsyncImage]
- assertTrue(imageNodes.all { it.shouldRedact })
+ assertTrue(imageNodes.all { it.shouldMask })
}
@Test
- fun `when redactAllImages is set to false all Image nodes are ignored`() {
- val activity = buildActivity(ComposeRedactionOptionsActivity::class.java).setup()
+ fun `when maskAllImages is set to false all Image nodes are unmasked`() {
+ val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllImages = false
+ experimental.sessionReplay.maskAllImages = false
}
val imageNodes = activity.get().collectNodesOfType(options)
assertEquals(1, imageNodes.size) // [AsyncImage]
- assertTrue(imageNodes.none { it.shouldRedact })
+ assertTrue(imageNodes.none { it.shouldMask })
}
@Test
- fun `when sentry-redact modifier is set redacts the node`() {
- ComposeRedactionOptionsActivity.textModifierApplier = { Modifier.sentryReplayRedact() }
- val activity = buildActivity(ComposeRedactionOptionsActivity::class.java).setup()
+ fun `when sentry-mask modifier is set masks the node`() {
+ ComposeMaskingOptionsActivity.textModifierApplier = { Modifier.sentryReplayMask() }
+ val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = false
+ experimental.sessionReplay.maskAllText = false
}
val textNodes = activity.get().collectNodesOfType(options)
assertEquals(4, textNodes.size) // [TextField, Text, Button, Activity Title]
textNodes.forEach {
if ((it.layout as? ComposeTextLayout)?.layout?.layoutInput?.text?.text == "Make Request") {
- assertTrue(it.shouldRedact)
+ assertTrue(it.shouldMask)
} else {
- assertFalse(it.shouldRedact)
+ assertFalse(it.shouldMask)
}
}
}
@Test
- fun `when sentry-ignore modifier is set ignores the node`() {
- ComposeRedactionOptionsActivity.textModifierApplier = { Modifier.sentryReplayIgnore() }
- val activity = buildActivity(ComposeRedactionOptionsActivity::class.java).setup()
+ fun `when sentry-unmask modifier is set unmasks the node`() {
+ ComposeMaskingOptionsActivity.textModifierApplier = { Modifier.sentryReplayUnmask() }
+ val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = true
+ experimental.sessionReplay.maskAllText = true
}
val textNodes = activity.get().collectNodesOfType(options)
assertEquals(4, textNodes.size) // [TextField, Text, Button, Activity Title]
textNodes.forEach {
if ((it.layout as? ComposeTextLayout)?.layout?.layoutInput?.text?.text == "Make Request") {
- assertFalse(it.shouldRedact)
+ assertFalse(it.shouldMask)
} else {
- assertTrue(it.shouldRedact)
+ assertTrue(it.shouldMask)
}
}
}
@Test
- fun `when view is not visible, does not redact the view`() {
- ComposeRedactionOptionsActivity.textModifierApplier = { Modifier.semantics { invisibleToUser() } }
- val activity = buildActivity(ComposeRedactionOptionsActivity::class.java).setup()
+ fun `when view is not visible, does not mask the view`() {
+ ComposeMaskingOptionsActivity.textModifierApplier = { Modifier.semantics { invisibleToUser() } }
+ val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = true
+ experimental.sessionReplay.maskAllText = true
}
val textNodes = activity.get().collectNodesOfType(options)
textNodes.forEach {
if ((it.layout as? ComposeTextLayout)?.layout?.layoutInput?.text?.text == "Make Request") {
- assertFalse(it.shouldRedact)
+ assertFalse(it.shouldMask)
} else {
- assertTrue(it.shouldRedact)
+ assertTrue(it.shouldMask)
}
}
}
@Test
- fun `when a container view is ignored its children are not ignored`() {
- ComposeRedactionOptionsActivity.containerModifierApplier = { Modifier.sentryReplayIgnore() }
- val activity = buildActivity(ComposeRedactionOptionsActivity::class.java).setup()
+ fun `when a container view is unmasked its children are not unmasked`() {
+ ComposeMaskingOptionsActivity.containerModifierApplier = { Modifier.sentryReplayUnmask() }
+ val activity = buildActivity(ComposeMaskingOptionsActivity::class.java).setup()
val options = SentryOptions()
@@ -176,9 +176,9 @@ class ComposeRedactionOptionsTest {
val imageNodes = allNodes.filterIsInstance()
val textNodes = allNodes.filterIsInstance()
val genericNodes = allNodes.filterIsInstance()
- assertTrue(imageNodes.all { it.shouldRedact })
- assertTrue(textNodes.all { it.shouldRedact })
- assertTrue(genericNodes.none { it.shouldRedact })
+ assertTrue(imageNodes.all { it.shouldMask })
+ assertTrue(textNodes.all { it.shouldMask })
+ assertTrue(genericNodes.none { it.shouldMask })
}
private inline fun Activity.collectNodesOfType(options: SentryOptions): List {
@@ -197,7 +197,7 @@ class ComposeRedactionOptionsTest {
}
}
-private class ComposeRedactionOptionsActivity : ComponentActivity() {
+private class ComposeMaskingOptionsActivity : ComponentActivity() {
companion object {
var textModifierApplier: (() -> Modifier)? = null
diff --git a/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/MaskingOptionsTest.kt b/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/MaskingOptionsTest.kt
new file mode 100644
index 00000000000..4a40e0a9150
--- /dev/null
+++ b/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/MaskingOptionsTest.kt
@@ -0,0 +1,278 @@
+package io.sentry.android.replay.viewhierarchy
+
+import android.app.Activity
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+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.test.ext.junit.runners.AndroidJUnit4
+import io.sentry.SentryOptions
+import io.sentry.android.replay.maskAllImages
+import io.sentry.android.replay.maskAllText
+import io.sentry.android.replay.sentryReplayMask
+import io.sentry.android.replay.sentryReplayUnmask
+import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.ImageViewHierarchyNode
+import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.TextViewHierarchyNode
+import org.junit.runner.RunWith
+import org.robolectric.Robolectric.buildActivity
+import org.robolectric.annotation.Config
+import kotlin.test.BeforeTest
+import kotlin.test.Test
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+@Config(sdk = [30])
+class MaskingOptionsTest {
+
+ @BeforeTest
+ fun setup() {
+ System.setProperty("robolectric.areWindowsMarkedVisible", "true")
+ }
+
+ @Test
+ fun `when maskAllText is set all TextView nodes are masked`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskAllText = true
+ }
+
+ val textNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.textView!!, null, 0, options)
+ val radioButtonNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.radioButton!!, null, 0, options)
+
+ assertTrue(textNode is TextViewHierarchyNode)
+ assertTrue(textNode.shouldMask)
+
+ assertTrue(radioButtonNode is TextViewHierarchyNode)
+ assertTrue(radioButtonNode.shouldMask)
+ }
+
+ @Test
+ fun `when maskAllText is set to false all TextView nodes are unmasked`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskAllText = false
+ }
+
+ val textNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.textView!!, null, 0, options)
+ val radioButtonNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.radioButton!!, null, 0, options)
+
+ assertTrue(textNode is TextViewHierarchyNode)
+ assertFalse(textNode.shouldMask)
+
+ assertTrue(radioButtonNode is TextViewHierarchyNode)
+ assertFalse(radioButtonNode.shouldMask)
+ }
+
+ @Test
+ fun `when maskAllImages is set all ImageView nodes are masked`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskAllImages = true
+ }
+
+ val imageNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.imageView!!, null, 0, options)
+
+ assertTrue(imageNode is ImageViewHierarchyNode)
+ assertTrue(imageNode.shouldMask)
+ }
+
+ @Test
+ fun `when maskAllImages is set to false all ImageView nodes are unmasked`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskAllImages = false
+ }
+
+ val imageNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.imageView!!, null, 0, options)
+
+ assertTrue(imageNode is ImageViewHierarchyNode)
+ assertFalse(imageNode.shouldMask)
+ }
+
+ @Test
+ fun `when sentry-mask tag is set mask the view`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskAllText = false
+ }
+
+ MaskingOptionsActivity.textView!!.tag = "sentry-mask"
+ val textNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.textView!!, null, 0, options)
+
+ assertTrue(textNode.shouldMask)
+ }
+
+ @Test
+ fun `when sentry-unmask tag is set unmasks the view`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskAllText = true
+ }
+
+ MaskingOptionsActivity.textView!!.tag = "sentry-unmask"
+ val textNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.textView!!, null, 0, options)
+
+ assertFalse(textNode.shouldMask)
+ }
+
+ @Test
+ fun `when sentry-privacy tag is set to mask masks the view`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskAllText = false
+ }
+
+ MaskingOptionsActivity.textView!!.sentryReplayMask()
+ val textNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.textView!!, null, 0, options)
+
+ assertTrue(textNode.shouldMask)
+ }
+
+ @Test
+ fun `when sentry-privacy tag is set to unmask unmasks the view`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskAllText = true
+ }
+
+ MaskingOptionsActivity.textView!!.sentryReplayUnmask()
+ val textNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.textView!!, null, 0, options)
+
+ assertFalse(textNode.shouldMask)
+ }
+
+ @Test
+ fun `when view is not visible, does not mask the view`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskAllText = true
+ }
+
+ MaskingOptionsActivity.textView!!.visibility = View.GONE
+ val textNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.textView!!, null, 0, options)
+
+ assertFalse(textNode.shouldMask)
+ }
+
+ @Test
+ fun `when added to mask list masks custom view`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskViewClasses.add(CustomView::class.java.canonicalName)
+ }
+
+ val customViewNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.customView!!, null, 0, options)
+
+ assertTrue(customViewNode.shouldMask)
+ }
+
+ @Test
+ fun `when subclass is added to ignored classes ignores all instances of that class`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.maskAllText = true // all TextView subclasses
+ experimental.sessionReplay.unmaskViewClasses.add(RadioButton::class.java.canonicalName)
+ }
+
+ val textNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.textView!!, null, 0, options)
+ val radioButtonNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.radioButton!!, null, 0, options)
+
+ assertTrue(textNode.shouldMask)
+ assertFalse(radioButtonNode.shouldMask)
+ }
+
+ @Test
+ fun `when a container view is ignored its children are not ignored`() {
+ buildActivity(MaskingOptionsActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.unmaskViewClasses.add(LinearLayout::class.java.canonicalName)
+ }
+
+ val linearLayoutNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.textView!!.parent as LinearLayout, null, 0, options)
+ val textNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.textView!!, null, 0, options)
+ val imageNode = ViewHierarchyNode.fromView(MaskingOptionsActivity.imageView!!, null, 0, options)
+
+ assertFalse(linearLayoutNode.shouldMask)
+ assertTrue(textNode.shouldMask)
+ assertTrue(imageNode.shouldMask)
+ }
+}
+
+private class CustomView(context: Context) : View(context) {
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ canvas.drawColor(Color.BLACK)
+ }
+}
+
+private class MaskingOptionsActivity : Activity() {
+
+ companion object {
+ var textView: TextView? = null
+ var radioButton: RadioButton? = null
+ var imageView: ImageView? = null
+ var customView: CustomView? = null
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val linearLayout = LinearLayout(this).apply {
+ setBackgroundColor(android.R.color.white)
+ orientation = LinearLayout.VERTICAL
+ layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
+ }
+
+ textView = TextView(this).apply {
+ text = "Hello, World!"
+ layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
+ }
+ linearLayout.addView(textView)
+
+ val image = this::class.java.classLoader.getResource("Tongariro.jpg")!!
+ imageView = ImageView(this).apply {
+ setImageDrawable(Drawable.createFromPath(image.path))
+ layoutParams = LayoutParams(50, 50).apply {
+ setMargins(0, 16, 0, 0)
+ }
+ }
+ linearLayout.addView(imageView)
+
+ radioButton = RadioButton(this).apply {
+ text = "Radio Button"
+ layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
+ setMargins(0, 16, 0, 0)
+ }
+ }
+ linearLayout.addView(radioButton)
+
+ customView = CustomView(this).apply {
+ layoutParams = LayoutParams(50, 50).apply {
+ setMargins(0, 16, 0, 0)
+ }
+ }
+ linearLayout.addView(customView)
+
+ setContentView(linearLayout)
+ }
+}
diff --git a/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/RedactionOptionsTest.kt b/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/RedactionOptionsTest.kt
deleted file mode 100644
index c1a50f7a62f..00000000000
--- a/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/RedactionOptionsTest.kt
+++ /dev/null
@@ -1,278 +0,0 @@
-package io.sentry.android.replay.viewhierarchy
-
-import android.app.Activity
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.drawable.Drawable
-import android.os.Bundle
-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.test.ext.junit.runners.AndroidJUnit4
-import io.sentry.SentryOptions
-import io.sentry.android.replay.redactAllImages
-import io.sentry.android.replay.redactAllText
-import io.sentry.android.replay.sentryReplayIgnore
-import io.sentry.android.replay.sentryReplayRedact
-import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.ImageViewHierarchyNode
-import io.sentry.android.replay.viewhierarchy.ViewHierarchyNode.TextViewHierarchyNode
-import org.junit.runner.RunWith
-import org.robolectric.Robolectric.buildActivity
-import org.robolectric.annotation.Config
-import kotlin.test.BeforeTest
-import kotlin.test.Test
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
-
-@RunWith(AndroidJUnit4::class)
-@Config(sdk = [30])
-class RedactionOptionsTest {
-
- @BeforeTest
- fun setup() {
- System.setProperty("robolectric.areWindowsMarkedVisible", "true")
- }
-
- @Test
- fun `when redactAllText is set all TextView nodes are redacted`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = true
- }
-
- val textNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.textView!!, null, 0, options)
- val radioButtonNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.radioButton!!, null, 0, options)
-
- assertTrue(textNode is TextViewHierarchyNode)
- assertTrue(textNode.shouldRedact)
-
- assertTrue(radioButtonNode is TextViewHierarchyNode)
- assertTrue(radioButtonNode.shouldRedact)
- }
-
- @Test
- fun `when redactAllText is set to false all TextView nodes are ignored`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = false
- }
-
- val textNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.textView!!, null, 0, options)
- val radioButtonNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.radioButton!!, null, 0, options)
-
- assertTrue(textNode is TextViewHierarchyNode)
- assertFalse(textNode.shouldRedact)
-
- assertTrue(radioButtonNode is TextViewHierarchyNode)
- assertFalse(radioButtonNode.shouldRedact)
- }
-
- @Test
- fun `when redactAllImages is set all ImageView nodes are redacted`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllImages = true
- }
-
- val imageNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.imageView!!, null, 0, options)
-
- assertTrue(imageNode is ImageViewHierarchyNode)
- assertTrue(imageNode.shouldRedact)
- }
-
- @Test
- fun `when redactAllImages is set to false all ImageView nodes are ignored`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllImages = false
- }
-
- val imageNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.imageView!!, null, 0, options)
-
- assertTrue(imageNode is ImageViewHierarchyNode)
- assertFalse(imageNode.shouldRedact)
- }
-
- @Test
- fun `when sentry-redact tag is set redacts the view`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = false
- }
-
- RedactionOptionsActivity.textView!!.tag = "sentry-redact"
- val textNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.textView!!, null, 0, options)
-
- assertTrue(textNode.shouldRedact)
- }
-
- @Test
- fun `when sentry-ignore tag is set ignores the view`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = true
- }
-
- RedactionOptionsActivity.textView!!.tag = "sentry-ignore"
- val textNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.textView!!, null, 0, options)
-
- assertFalse(textNode.shouldRedact)
- }
-
- @Test
- fun `when sentry-privacy tag is set to redact redacts the view`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = false
- }
-
- RedactionOptionsActivity.textView!!.sentryReplayRedact()
- val textNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.textView!!, null, 0, options)
-
- assertTrue(textNode.shouldRedact)
- }
-
- @Test
- fun `when sentry-privacy tag is set to ignore ignores the view`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = true
- }
-
- RedactionOptionsActivity.textView!!.sentryReplayIgnore()
- val textNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.textView!!, null, 0, options)
-
- assertFalse(textNode.shouldRedact)
- }
-
- @Test
- fun `when view is not visible, does not redact the view`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = true
- }
-
- RedactionOptionsActivity.textView!!.visibility = View.GONE
- val textNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.textView!!, null, 0, options)
-
- assertFalse(textNode.shouldRedact)
- }
-
- @Test
- fun `when added to redact list redacts custom view`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactViewClasses.add(CustomView::class.java.canonicalName)
- }
-
- val customViewNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.customView!!, null, 0, options)
-
- assertTrue(customViewNode.shouldRedact)
- }
-
- @Test
- fun `when subclass is added to ignored classes ignores all instances of that class`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.redactAllText = true // all TextView subclasses
- experimental.sessionReplay.ignoreViewClasses.add(RadioButton::class.java.canonicalName)
- }
-
- val textNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.textView!!, null, 0, options)
- val radioButtonNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.radioButton!!, null, 0, options)
-
- assertTrue(textNode.shouldRedact)
- assertFalse(radioButtonNode.shouldRedact)
- }
-
- @Test
- fun `when a container view is ignored its children are not ignored`() {
- buildActivity(RedactionOptionsActivity::class.java).setup()
-
- val options = SentryOptions().apply {
- experimental.sessionReplay.ignoreViewClasses.add(LinearLayout::class.java.canonicalName)
- }
-
- val linearLayoutNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.textView!!.parent as LinearLayout, null, 0, options)
- val textNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.textView!!, null, 0, options)
- val imageNode = ViewHierarchyNode.fromView(RedactionOptionsActivity.imageView!!, null, 0, options)
-
- assertFalse(linearLayoutNode.shouldRedact)
- assertTrue(textNode.shouldRedact)
- assertTrue(imageNode.shouldRedact)
- }
-}
-
-private class CustomView(context: Context) : View(context) {
-
- override fun onDraw(canvas: Canvas) {
- super.onDraw(canvas)
- canvas.drawColor(Color.BLACK)
- }
-}
-
-private class RedactionOptionsActivity : Activity() {
-
- companion object {
- var textView: TextView? = null
- var radioButton: RadioButton? = null
- var imageView: ImageView? = null
- var customView: CustomView? = null
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val linearLayout = LinearLayout(this).apply {
- setBackgroundColor(android.R.color.white)
- orientation = LinearLayout.VERTICAL
- layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
- }
-
- textView = TextView(this).apply {
- text = "Hello, World!"
- layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
- }
- linearLayout.addView(textView)
-
- val image = this::class.java.classLoader.getResource("Tongariro.jpg")!!
- imageView = ImageView(this).apply {
- setImageDrawable(Drawable.createFromPath(image.path))
- layoutParams = LayoutParams(50, 50).apply {
- setMargins(0, 16, 0, 0)
- }
- }
- linearLayout.addView(imageView)
-
- radioButton = RadioButton(this).apply {
- text = "Radio Button"
- layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
- setMargins(0, 16, 0, 0)
- }
- }
- linearLayout.addView(radioButton)
-
- customView = CustomView(this).apply {
- layoutParams = LayoutParams(50, 50).apply {
- setMargins(0, 16, 0, 0)
- }
- }
- linearLayout.addView(customView)
-
- setContentView(linearLayout)
- }
-}
diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml
index 703685d6f02..058ad3710c8 100644
--- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml
+++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml
@@ -166,7 +166,6 @@
-
-
+
diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/compose/ComposeActivity.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/compose/ComposeActivity.kt
index 03d9e8d049b..3d2e670495d 100644
--- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/compose/ComposeActivity.kt
+++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/compose/ComposeActivity.kt
@@ -36,7 +36,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import coil.compose.AsyncImage
-import io.sentry.android.replay.sentryReplayIgnore
+import io.sentry.android.replay.sentryReplayUnmask
import io.sentry.compose.SentryTraced
import io.sentry.compose.withSentryObservableEffect
import io.sentry.samples.android.GithubAPI
@@ -145,7 +145,7 @@ fun Github(
.testTag("button_list_repos_async")
.padding(top = 32.dp)
) {
- Text("Make Request", modifier = Modifier.sentryReplayIgnore())
+ Text("Make Request", modifier = Modifier.sentryReplayUnmask())
}
}
}
diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api
index f8dc3b52eef..ac1eb2bc8a1 100644
--- a/sentry/api/sentry.api
+++ b/sentry/api/sentry.api
@@ -2711,27 +2711,32 @@ public final class io/sentry/SentryReplayEvent$ReplayType$Deserializer : io/sent
}
public final class io/sentry/SentryReplayOptions {
+ public static final field ANDROIDX_MEDIA_VIEW_CLASS_NAME Ljava/lang/String;
+ public static final field EXOPLAYER_CLASS_NAME Ljava/lang/String;
+ public static final field EXOPLAYER_STYLED_CLASS_NAME Ljava/lang/String;
public static final field IMAGE_VIEW_CLASS_NAME Ljava/lang/String;
public static final field TEXT_VIEW_CLASS_NAME Ljava/lang/String;
+ public static final field VIDEO_VIEW_CLASS_NAME Ljava/lang/String;
+ public static final field WEB_VIEW_CLASS_NAME Ljava/lang/String;
public fun (Ljava/lang/Double;Ljava/lang/Double;)V
public fun (Z)V
- public fun addIgnoreViewClass (Ljava/lang/String;)V
- public fun addRedactViewClass (Ljava/lang/String;)V
+ public fun addMaskViewClass (Ljava/lang/String;)V
+ public fun addUnmaskViewClass (Ljava/lang/String;)V
public fun getErrorReplayDuration ()J
public fun getFrameRate ()I
- public fun getIgnoreViewClasses ()Ljava/util/Set;
+ public fun getMaskViewClasses ()Ljava/util/Set;
public fun getOnErrorSampleRate ()Ljava/lang/Double;
public fun getQuality ()Lio/sentry/SentryReplayOptions$SentryReplayQuality;
- public fun getRedactViewClasses ()Ljava/util/Set;
public fun getSessionDuration ()J
public fun getSessionSampleRate ()Ljava/lang/Double;
public fun getSessionSegmentDuration ()J
+ public fun getUnmaskViewClasses ()Ljava/util/Set;
public fun isSessionReplayEnabled ()Z
public fun isSessionReplayForErrorsEnabled ()Z
+ public fun setMaskAllImages (Z)V
+ public fun setMaskAllText (Z)V
public fun setOnErrorSampleRate (Ljava/lang/Double;)V
public fun setQuality (Lio/sentry/SentryReplayOptions$SentryReplayQuality;)V
- public fun setRedactAllImages (Z)V
- public fun setRedactAllText (Z)V
public fun setSessionSampleRate (Ljava/lang/Double;)V
}
diff --git a/sentry/src/main/java/io/sentry/SentryReplayOptions.java b/sentry/src/main/java/io/sentry/SentryReplayOptions.java
index 097f72c9210..0c99085726a 100644
--- a/sentry/src/main/java/io/sentry/SentryReplayOptions.java
+++ b/sentry/src/main/java/io/sentry/SentryReplayOptions.java
@@ -11,6 +11,12 @@ public final class SentryReplayOptions {
public static final String TEXT_VIEW_CLASS_NAME = "android.widget.TextView";
public static final String IMAGE_VIEW_CLASS_NAME = "android.widget.ImageView";
+ public static final String WEB_VIEW_CLASS_NAME = "android.webkit.WebView";
+ public static final String VIDEO_VIEW_CLASS_NAME = "android.widget.VideoView";
+ public static final String ANDROIDX_MEDIA_VIEW_CLASS_NAME = "androidx.media3.ui.PlayerView";
+ public static final String EXOPLAYER_CLASS_NAME = "com.google.android.exoplayer2.ui.PlayerView";
+ public static final String EXOPLAYER_STYLED_CLASS_NAME =
+ "com.google.android.exoplayer2.ui.StyledPlayerView";
public enum SentryReplayQuality {
/** Video Scale: 80% Bit Rate: 50.000 */
@@ -52,19 +58,19 @@ public enum SentryReplayQuality {
private @Nullable Double onErrorSampleRate;
/**
- * Redact all views with the specified class names. The class name is the fully qualified class
- * name of the view, e.g. android.widget.TextView. The subclasses of the specified classes will be
- * redacted as well.
+ * Mask all views with the specified class names. The class name is the fully qualified class name
+ * of the view, e.g. android.widget.TextView. The subclasses of the specified classes will be
+ * masked as well.
*
* If you're using an obfuscation tool, make sure to add the respective proguard rules to keep
* the class names.
*
*
Default is empty.
*/
- private Set redactViewClasses = new CopyOnWriteArraySet<>();
+ private Set maskViewClasses = new CopyOnWriteArraySet<>();
/**
- * Ignore all views with the specified class names from redaction. The class name is the fully
+ * Ignore all views with the specified class names from masking. The class name is the fully
* qualified class name of the view, e.g. android.widget.TextView. The subclasses of the specified
* classes will be ignored as well.
*
@@ -73,7 +79,7 @@ public enum SentryReplayQuality {
*
* Default is empty.
*/
- private Set ignoreViewClasses = new CopyOnWriteArraySet<>();
+ private Set unmaskViewClasses = new CopyOnWriteArraySet<>();
/**
* Defines the quality of the session replay. The higher the quality, the more accurate the replay
@@ -98,8 +104,13 @@ public enum SentryReplayQuality {
public SentryReplayOptions(final boolean empty) {
if (!empty) {
- setRedactAllText(true);
- setRedactAllImages(true);
+ setMaskAllText(true);
+ setMaskAllImages(true);
+ maskViewClasses.add(WEB_VIEW_CLASS_NAME);
+ maskViewClasses.add(VIDEO_VIEW_CLASS_NAME);
+ maskViewClasses.add(ANDROIDX_MEDIA_VIEW_CLASS_NAME);
+ maskViewClasses.add(EXOPLAYER_CLASS_NAME);
+ maskViewClasses.add(EXOPLAYER_STYLED_CLASS_NAME);
}
}
@@ -149,55 +160,55 @@ public void setSessionSampleRate(final @Nullable Double sessionSampleRate) {
}
/**
- * Redact all text content. Draws a rectangle of text bounds with text color on top. By default
- * only views extending TextView are redacted.
+ * Mask all text content. Draws a rectangle of text bounds with text color on top. By default only
+ * views extending TextView are masked.
*
* Default is enabled.
*/
- public void setRedactAllText(final boolean redactAllText) {
- if (redactAllText) {
- addRedactViewClass(TEXT_VIEW_CLASS_NAME);
- ignoreViewClasses.remove(TEXT_VIEW_CLASS_NAME);
+ public void setMaskAllText(final boolean maskAllText) {
+ if (maskAllText) {
+ addMaskViewClass(TEXT_VIEW_CLASS_NAME);
+ unmaskViewClasses.remove(TEXT_VIEW_CLASS_NAME);
} else {
- addIgnoreViewClass(TEXT_VIEW_CLASS_NAME);
- redactViewClasses.remove(TEXT_VIEW_CLASS_NAME);
+ addUnmaskViewClass(TEXT_VIEW_CLASS_NAME);
+ maskViewClasses.remove(TEXT_VIEW_CLASS_NAME);
}
}
/**
- * Redact all image content. Draws a rectangle of image bounds with image's dominant color on top.
+ * Mask all image content. Draws a rectangle of image bounds with image's dominant color on top.
* By default only views extending ImageView with BitmapDrawable or custom Drawable type are
- * redacted. ColorDrawable, InsetDrawable, VectorDrawable are all considered non-PII, as they come
+ * masked. ColorDrawable, InsetDrawable, VectorDrawable are all considered non-PII, as they come
* from the apk.
*
*
Default is enabled.
*/
- public void setRedactAllImages(final boolean redactAllImages) {
- if (redactAllImages) {
- addRedactViewClass(IMAGE_VIEW_CLASS_NAME);
- ignoreViewClasses.remove(IMAGE_VIEW_CLASS_NAME);
+ public void setMaskAllImages(final boolean maskAllImages) {
+ if (maskAllImages) {
+ addMaskViewClass(IMAGE_VIEW_CLASS_NAME);
+ unmaskViewClasses.remove(IMAGE_VIEW_CLASS_NAME);
} else {
- addIgnoreViewClass(IMAGE_VIEW_CLASS_NAME);
- redactViewClasses.remove(IMAGE_VIEW_CLASS_NAME);
+ addUnmaskViewClass(IMAGE_VIEW_CLASS_NAME);
+ maskViewClasses.remove(IMAGE_VIEW_CLASS_NAME);
}
}
@NotNull
- public Set getRedactViewClasses() {
- return this.redactViewClasses;
+ public Set getMaskViewClasses() {
+ return this.maskViewClasses;
}
- public void addRedactViewClass(final @NotNull String className) {
- this.redactViewClasses.add(className);
+ public void addMaskViewClass(final @NotNull String className) {
+ this.maskViewClasses.add(className);
}
@NotNull
- public Set getIgnoreViewClasses() {
- return this.ignoreViewClasses;
+ public Set getUnmaskViewClasses() {
+ return this.unmaskViewClasses;
}
- public void addIgnoreViewClass(final @NotNull String className) {
- this.ignoreViewClasses.add(className);
+ public void addUnmaskViewClass(final @NotNull String className) {
+ this.unmaskViewClasses.add(className);
}
@ApiStatus.Internal