(Lio/sentry/SentryOptions;Lio/sentry/android/replay/gestures/TouchRecorderCallback;)V
public fun onRootViewsChanged (Landroid/view/View;Z)V
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt
index c1bfeb1e526..3db92ea5d80 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt
@@ -80,6 +80,7 @@ public class ReplayCache(
if (replayCacheDir == null || bitmap.isRecycled) {
return
}
+ replayCacheDir?.mkdirs()
val screenshot = File(replayCacheDir, "$frameTimestamp.jpg").also {
it.createNewFile()
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
new file mode 100644
index 00000000000..e3e6605a968
--- /dev/null
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/SessionReplayOptions.kt
@@ -0,0 +1,31 @@
+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
+// 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.
+ *
+ * Default is enabled.
+ */
+var SentryReplayOptions.redactAllText: Boolean
+ @Deprecated("Getter is unsupported.", level = DeprecationLevel.ERROR)
+ get() = error("Getter not supported")
+ set(value) = setRedactAllText(value)
+
+/**
+ * Redact 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
+ * from the apk.
+ *
+ *
Default is enabled.
+ */
+var SentryReplayOptions.redactAllImages: Boolean
+ @Deprecated("Getter is unsupported.", level = DeprecationLevel.ERROR)
+ get() = error("Getter not supported")
+ set(value) = setRedactAllImages(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
new file mode 100644
index 00000000000..37061a5b77c
--- /dev/null
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ViewExtensions.kt
@@ -0,0 +1,18 @@
+package io.sentry.android.replay
+
+import android.view.View
+
+/**
+ * Marks this view to be redacted in session replay.
+ */
+fun View.sentryReplayRedact() {
+ setTag(R.id.sentry_privacy, "redact")
+}
+
+/**
+ * Marks this view to be ignored from redaction in session.
+ * All its content will be visible in the replay, use with caution.
+ */
+fun View.sentryReplayIgnore() {
+ setTag(R.id.sentry_privacy, "ignore")
+}
diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/Windows.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/Windows.kt
index 8ef595f1934..48c7eb58138 100644
--- a/sentry-android-replay/src/main/java/io/sentry/android/replay/Windows.kt
+++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/Windows.kt
@@ -134,7 +134,7 @@ internal fun interface OnRootViewsChangedListener {
/**
* A utility that holds the list of root views that WindowManager updates.
*/
-internal class RootViewsSpy private constructor() {
+internal object RootViewsSpy {
val listeners: CopyOnWriteArrayList = object : CopyOnWriteArrayList() {
override fun add(element: OnRootViewsChangedListener?): Boolean {
@@ -168,15 +168,13 @@ internal class RootViewsSpy private constructor() {
}
}
- companion object {
- fun install(): RootViewsSpy {
- return RootViewsSpy().apply {
- // had to do this as a first message of the main thread queue, otherwise if this is
- // called from ContentProvider, it might be too early and the listener won't be installed
- Handler(Looper.getMainLooper()).postAtFrontOfQueue {
- WindowManagerSpy.swapWindowManagerGlobalMViews { mViews ->
- delegatingViewList.apply { addAll(mViews) }
- }
+ fun install(): RootViewsSpy {
+ return apply {
+ // had to do this as a first message of the main thread queue, otherwise if this is
+ // called from ContentProvider, it might be too early and the listener won't be installed
+ Handler(Looper.getMainLooper()).postAtFrontOfQueue {
+ WindowManagerSpy.swapWindowManagerGlobalMViews { mViews ->
+ delegatingViewList.apply { addAll(mViews) }
}
}
}
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 145cefff3d4..90b96f134bb 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
@@ -7,6 +7,7 @@ import android.view.View
import android.widget.ImageView
import android.widget.TextView
import io.sentry.SentryOptions
+import io.sentry.android.replay.R
import io.sentry.android.replay.util.isRedactable
import io.sentry.android.replay.util.isVisibleToUser
import io.sentry.android.replay.util.totalPaddingTopSafe
@@ -233,14 +234,46 @@ sealed class ViewHierarchyNode(
}
}
- private fun shouldRedact(view: View, options: SentryOptions): Boolean {
- return options.experimental.sessionReplay.redactClasses.contains(view.javaClass.canonicalName)
+ private const val SENTRY_IGNORE_TAG = "sentry-ignore"
+ private const val SENTRY_REDACT_TAG = "sentry-redact"
+
+ private fun Class<*>.isAssignableFrom(set: Set): Boolean {
+ var cls: Class<*>? = this
+ while (cls != null) {
+ val canonicalName = cls.canonicalName
+ if (canonicalName != null && set.contains(canonicalName)) {
+ return true
+ }
+ cls = cls.superclass
+ }
+ 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"
+ ) {
+ return false
+ }
+
+ if ((tag as? String)?.lowercase()?.contains(SENTRY_REDACT_TAG) == true ||
+ getTag(R.id.sentry_privacy) == "redact"
+ ) {
+ return true
+ }
+
+ if (this.javaClass.isAssignableFrom(options.experimental.sessionReplay.ignoreViewClasses)) {
+ return false
+ }
+
+ return this.javaClass.isAssignableFrom(options.experimental.sessionReplay.redactViewClasses)
}
fun fromView(view: View, parent: ViewHierarchyNode?, distance: Int, options: SentryOptions): ViewHierarchyNode {
val (isVisible, visibleRect) = view.isVisibleToUser()
- when {
- view is TextView && options.experimental.sessionReplay.redactAllText -> {
+ val shouldRedact = isVisible && view.shouldRedact(options)
+ when (view) {
+ is TextView -> {
parent.setImportantForCaptureToAncestors(true)
return TextViewHierarchyNode(
layout = view.layout,
@@ -252,7 +285,7 @@ sealed class ViewHierarchyNode(
width = view.width,
height = view.height,
elevation = (parent?.elevation ?: 0f) + view.elevation,
- shouldRedact = isVisible,
+ shouldRedact = shouldRedact,
distance = distance,
parent = parent,
isImportantForContentCapture = true,
@@ -261,7 +294,7 @@ sealed class ViewHierarchyNode(
)
}
- view is ImageView && options.experimental.sessionReplay.redactAllImages -> {
+ is ImageView -> {
parent.setImportantForCaptureToAncestors(true)
return ImageViewHierarchyNode(
x = view.x,
@@ -273,7 +306,7 @@ sealed class ViewHierarchyNode(
parent = parent,
isVisible = isVisible,
isImportantForContentCapture = true,
- shouldRedact = isVisible && view.drawable?.isRedactable() == true,
+ shouldRedact = shouldRedact && view.drawable?.isRedactable() == true,
visibleRect = visibleRect
)
}
@@ -287,7 +320,7 @@ sealed class ViewHierarchyNode(
(parent?.elevation ?: 0f) + view.elevation,
distance = distance,
parent = parent,
- shouldRedact = isVisible && shouldRedact(view, options),
+ shouldRedact = shouldRedact,
isImportantForContentCapture = false, /* will be set by children */
isVisible = isVisible,
visibleRect = visibleRect
diff --git a/sentry-android-replay/src/main/res/public.xml b/sentry-android-replay/src/main/res/public.xml
deleted file mode 100644
index 379be515be2..00000000000
--- a/sentry-android-replay/src/main/res/public.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/sentry-android-replay/src/main/res/values/public.xml b/sentry-android-replay/src/main/res/values/public.xml
new file mode 100644
index 00000000000..cc60000bcd3
--- /dev/null
+++ b/sentry-android-replay/src/main/res/values/public.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
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
new file mode 100644
index 00000000000..8ffffd046da
--- /dev/null
+++ b/sentry-android-replay/src/test/java/io/sentry/android/replay/viewhierarchy/RedactionOptionsTest.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.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.Before
+import org.junit.runner.RunWith
+import org.robolectric.Robolectric.buildActivity
+import org.robolectric.annotation.Config
+import kotlin.test.Test
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+@Config(sdk = [30])
+class RedactionOptionsTest {
+
+ @Before
+ fun setup() {
+ System.setProperty("robolectric.areWindowsMarkedVisible", "true")
+ }
+
+ @Test
+ fun `when redactAllText is set all TextView nodes are redacted`() {
+ buildActivity(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.redactAllText = true
+ }
+
+ val textNode = ViewHierarchyNode.fromView(ExampleActivity.textView!!, null, 0, options)
+ val radioButtonNode = ViewHierarchyNode.fromView(ExampleActivity.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(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.redactAllText = false
+ }
+
+ val textNode = ViewHierarchyNode.fromView(ExampleActivity.textView!!, null, 0, options)
+ val radioButtonNode = ViewHierarchyNode.fromView(ExampleActivity.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(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.redactAllImages = true
+ }
+
+ val imageNode = ViewHierarchyNode.fromView(ExampleActivity.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(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.redactAllImages = false
+ }
+
+ val imageNode = ViewHierarchyNode.fromView(ExampleActivity.imageView!!, null, 0, options)
+
+ assertTrue(imageNode is ImageViewHierarchyNode)
+ assertFalse(imageNode.shouldRedact)
+ }
+
+ @Test
+ fun `when sentry-redact tag is set redacts the view`() {
+ buildActivity(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.redactAllText = false
+ }
+
+ ExampleActivity.textView!!.tag = "sentry-redact"
+ val textNode = ViewHierarchyNode.fromView(ExampleActivity.textView!!, null, 0, options)
+
+ assertTrue(textNode.shouldRedact)
+ }
+
+ @Test
+ fun `when sentry-ignore tag is set ignores the view`() {
+ buildActivity(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.redactAllText = true
+ }
+
+ ExampleActivity.textView!!.tag = "sentry-ignore"
+ val textNode = ViewHierarchyNode.fromView(ExampleActivity.textView!!, null, 0, options)
+
+ assertFalse(textNode.shouldRedact)
+ }
+
+ @Test
+ fun `when sentry-privacy tag is set to redact redacts the view`() {
+ buildActivity(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.redactAllText = false
+ }
+
+ ExampleActivity.textView!!.sentryReplayRedact()
+ val textNode = ViewHierarchyNode.fromView(ExampleActivity.textView!!, null, 0, options)
+
+ assertTrue(textNode.shouldRedact)
+ }
+
+ @Test
+ fun `when sentry-privacy tag is set to ignore ignores the view`() {
+ buildActivity(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.redactAllText = true
+ }
+
+ ExampleActivity.textView!!.sentryReplayIgnore()
+ val textNode = ViewHierarchyNode.fromView(ExampleActivity.textView!!, null, 0, options)
+
+ assertFalse(textNode.shouldRedact)
+ }
+
+ @Test
+ fun `when view is not visible, does not redact the view`() {
+ buildActivity(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.redactAllText = true
+ }
+
+ ExampleActivity.textView!!.visibility = View.GONE
+ val textNode = ViewHierarchyNode.fromView(ExampleActivity.textView!!, null, 0, options)
+
+ assertFalse(textNode.shouldRedact)
+ }
+
+ @Test
+ fun `when added to redact list redacts custom view`() {
+ buildActivity(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.redactViewClasses.add(CustomView::class.java.canonicalName)
+ }
+
+ val customViewNode = ViewHierarchyNode.fromView(ExampleActivity.customView!!, null, 0, options)
+
+ assertTrue(customViewNode.shouldRedact)
+ }
+
+ @Test
+ fun `when subclass is added to ignored classes ignores all instances of that class`() {
+ buildActivity(ExampleActivity::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(ExampleActivity.textView!!, null, 0, options)
+ val radioButtonNode = ViewHierarchyNode.fromView(ExampleActivity.radioButton!!, null, 0, options)
+
+ assertTrue(textNode.shouldRedact)
+ assertFalse(radioButtonNode.shouldRedact)
+ }
+
+ @Test
+ fun `when a container view is ignored its children are not ignored`() {
+ buildActivity(ExampleActivity::class.java).setup()
+
+ val options = SentryOptions().apply {
+ experimental.sessionReplay.ignoreViewClasses.add(LinearLayout::class.java.canonicalName)
+ }
+
+ val linearLayoutNode = ViewHierarchyNode.fromView(ExampleActivity.textView!!.parent as LinearLayout, null, 0, options)
+ val textNode = ViewHierarchyNode.fromView(ExampleActivity.textView!!, null, 0, options)
+ val imageNode = ViewHierarchyNode.fromView(ExampleActivity.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 ExampleActivity : 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/api/sentry.api b/sentry/api/sentry.api
index ceab2fc3264..e53d175081a 100644
--- a/sentry/api/sentry.api
+++ b/sentry/api/sentry.api
@@ -2699,16 +2699,18 @@ public final class io/sentry/SentryReplayEvent$ReplayType$Deserializer : io/sent
}
public final class io/sentry/SentryReplayOptions {
+ public static final field IMAGE_VIEW_CLASS_NAME Ljava/lang/String;
+ public static final field TEXT_VIEW_CLASS_NAME Ljava/lang/String;
public fun ()V
public fun (Ljava/lang/Double;Ljava/lang/Double;)V
- public fun addClassToRedact (Ljava/lang/String;)V
+ public fun addIgnoreViewClass (Ljava/lang/String;)V
+ public fun addRedactViewClass (Ljava/lang/String;)V
public fun getErrorReplayDuration ()J
public fun getFrameRate ()I
+ public fun getIgnoreViewClasses ()Ljava/util/Set;
public fun getOnErrorSampleRate ()Ljava/lang/Double;
public fun getQuality ()Lio/sentry/SentryReplayOptions$SentryReplayQuality;
- public fun getRedactAllImages ()Z
- public fun getRedactAllText ()Z
- public fun getRedactClasses ()Ljava/util/Set;
+ public fun getRedactViewClasses ()Ljava/util/Set;
public fun getSessionDuration ()J
public fun getSessionSampleRate ()Ljava/lang/Double;
public fun getSessionSegmentDuration ()J
diff --git a/sentry/src/main/java/io/sentry/SentryReplayOptions.java b/sentry/src/main/java/io/sentry/SentryReplayOptions.java
index 0024708048d..7656b088a15 100644
--- a/sentry/src/main/java/io/sentry/SentryReplayOptions.java
+++ b/sentry/src/main/java/io/sentry/SentryReplayOptions.java
@@ -9,6 +9,9 @@
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 enum SentryReplayQuality {
/** Video Scale: 80% Bit Rate: 50.000 */
LOW(0.8f, 50_000),
@@ -49,30 +52,28 @@ public enum SentryReplayQuality {
private @Nullable Double onErrorSampleRate;
/**
- * Redact all text content. Draws a rectangle of text bounds with text color on top. By default
- * only views extending TextView are redacted.
+ * 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.
*
- * Default is enabled.
- */
- private boolean redactAllText = true;
-
- /**
- * Redact 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
- * from the apk.
+ *
If you're using an obfuscation tool, make sure to add the respective proguard rules to keep
+ * the class names.
*
- *
Default is enabled.
+ *
Default is empty.
*/
- private boolean redactAllImages = true;
+ private Set redactViewClasses = new CopyOnWriteArraySet<>();
/**
- * 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.
+ * Ignore all views with the specified class names from redaction. 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.
+ *
+ * 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 redactClasses = new CopyOnWriteArraySet<>();
+ private Set ignoreViewClasses = new CopyOnWriteArraySet<>();
/**
* Defines the quality of the session replay. The higher the quality, the more accurate the replay
@@ -95,10 +96,14 @@ public enum SentryReplayQuality {
/** The maximum duration of a full session replay, defaults to 1h. */
private long sessionDuration = 60 * 60 * 1000L;
- public SentryReplayOptions() {}
+ public SentryReplayOptions() {
+ setRedactAllText(true);
+ setRedactAllImages(true);
+ }
public SentryReplayOptions(
final @Nullable Double sessionSampleRate, final @Nullable Double onErrorSampleRate) {
+ this();
this.sessionSampleRate = sessionSampleRate;
this.onErrorSampleRate = onErrorSampleRate;
}
@@ -141,28 +146,56 @@ public void setSessionSampleRate(final @Nullable Double sessionSampleRate) {
this.sessionSampleRate = sessionSampleRate;
}
- public boolean getRedactAllText() {
- return redactAllText;
+ /**
+ * Redact all text content. Draws a rectangle of text bounds with text color on top. By default
+ * only views extending TextView are redacted.
+ *
+ * Default is enabled.
+ */
+ public void setRedactAllText(final boolean redactAllText) {
+ if (redactAllText) {
+ addRedactViewClass(TEXT_VIEW_CLASS_NAME);
+ ignoreViewClasses.remove(TEXT_VIEW_CLASS_NAME);
+ } else {
+ addIgnoreViewClass(TEXT_VIEW_CLASS_NAME);
+ redactViewClasses.remove(TEXT_VIEW_CLASS_NAME);
+ }
}
- public void setRedactAllText(final boolean redactAllText) {
- this.redactAllText = redactAllText;
+ /**
+ * Redact 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
+ * 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);
+ } else {
+ addIgnoreViewClass(IMAGE_VIEW_CLASS_NAME);
+ redactViewClasses.remove(IMAGE_VIEW_CLASS_NAME);
+ }
}
- public boolean getRedactAllImages() {
- return redactAllImages;
+ @NotNull
+ public Set getRedactViewClasses() {
+ return this.redactViewClasses;
}
- public void setRedactAllImages(final boolean redactAllImages) {
- this.redactAllImages = redactAllImages;
+ public void addRedactViewClass(final @NotNull String className) {
+ this.redactViewClasses.add(className);
}
- public Set getRedactClasses() {
- return this.redactClasses;
+ @NotNull
+ public Set getIgnoreViewClasses() {
+ return this.ignoreViewClasses;
}
- public void addClassToRedact(final String className) {
- this.redactClasses.add(className);
+ public void addIgnoreViewClass(final @NotNull String className) {
+ this.ignoreViewClasses.add(className);
}
@ApiStatus.Internal