Skip to content

Commit 0ab3bb3

Browse files
authored
[SR] Change terminology from redact/ignore to mask/unmask (#3741)
* WIP * Compose works * Custom redaction works for Compose * Formatting * Clean up * Test * Add tests * Changelog * Change terminology from redact/ignore to mask/unmask * Changelog * [SR] Mask web and video views (#3775) * Replace logo with sentry * Add missing proguard rules * formatting * Faster boundsInWindow for compose * api dump * Dont use liveliterals * Remove redundant test * Increase timeout in failing test
1 parent 6548825 commit 0ab3bb3

File tree

22 files changed

+485
-460
lines changed

22 files changed

+485
-460
lines changed

CHANGELOG.md

+9-7
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66

77
- Add support for `feedback` envelope header item type ([#3687](https://github.com/getsentry/sentry-java/pull/3687))
88
- Add breadcrumb.origin field ([#3727](https://github.com/getsentry/sentry-java/pull/3727))
9-
- 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))
10-
- `android:tag="sentry-redact|sentry-ignore"` in XML or `view.setTag("sentry-redact|sentry-ignore")` in code tags
11-
- if you already have a tag set for a view, you can set a tag by id: `<tag android:id="@id/sentry_privacy" android:value="redact|ignore"/>` in XML or `view.setTag(io.sentry.android.replay.R.id.sentry_privacy, "redact|ignore")` in code
12-
- `view.sentryReplayRedact()` or `view.sentryReplayIgnore()` extension functions
13-
- 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
14-
- 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")`
9+
- 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))
10+
- `android:tag="sentry-mask|sentry-unmask"` in XML or `view.setTag("sentry-mask|sentry-unmask")` in code tags
11+
- if you already have a tag set for a view, you can set a tag by id: `<tag android:id="@id/sentry_privacy" android:value="mask|unmask"/>` in XML or `view.setTag(io.sentry.android.replay.R.id.sentry_privacy, "mask|unmask")` in code
12+
- `view.sentryReplayMask()` or `view.sentryReplayUnmask()` extension functions
13+
- 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
14+
- 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")`
1515
- If you're using code obfuscation, adjust your proguard-rules accordingly, so your custom view class name is not minified
1616
- Session Replay: Support Jetpack Compose masking ([#3739](https://github.com/getsentry/sentry-java/pull/3739))
17-
- To selectively mask/unmask @Composables, use `Modifier.sentryReplayRedact()` and `Modifier.sentryReplayIgnore()` modifiers
17+
- To selectively mask/unmask @Composables, use `Modifier.sentryReplayMask()` and `Modifier.sentryReplayUnmask()` modifiers
18+
- Session Replay: Mask `WebView`, `VideoView` and `androidx.media3.ui.PlayerView` by default ([#3775](https://github.com/getsentry/sentry-java/pull/3775))
1819

1920
### Fixes
2021

@@ -29,6 +30,7 @@
2930

3031
- `options.experimental.sessionReplay.errorSampleRate` was renamed to `options.experimental.sessionReplay.onErrorSampleRate` ([#3637](https://github.com/getsentry/sentry-java/pull/3637))
3132
- 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))
33+
- Change `redactAllText` and `redactAllImages` to `maskAllText` and `maskAllImages` ([#3741](https://github.com/getsentry/sentry-java/pull/3741))
3234

3335
## 7.14.0
3436

sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ final class ManifestMetadataReader {
108108

109109
static final String REPLAYS_ERROR_SAMPLE_RATE = "io.sentry.session-replay.on-error-sample-rate";
110110

111-
static final String REPLAYS_REDACT_ALL_TEXT = "io.sentry.session-replay.redact-all-text";
111+
static final String REPLAYS_MASK_ALL_TEXT = "io.sentry.session-replay.mask-all-text";
112112

113-
static final String REPLAYS_REDACT_ALL_IMAGES = "io.sentry.session-replay.redact-all-images";
113+
static final String REPLAYS_MASK_ALL_IMAGES = "io.sentry.session-replay.mask-all-images";
114114

115115
/** ManifestMetadataReader ctor */
116116
private ManifestMetadataReader() {}
@@ -409,12 +409,12 @@ static void applyMetadata(
409409
options
410410
.getExperimental()
411411
.getSessionReplay()
412-
.setRedactAllText(readBool(metadata, logger, REPLAYS_REDACT_ALL_TEXT, true));
412+
.setMaskAllText(readBool(metadata, logger, REPLAYS_MASK_ALL_TEXT, true));
413413

414414
options
415415
.getExperimental()
416416
.getSessionReplay()
417-
.setRedactAllImages(readBool(metadata, logger, REPLAYS_REDACT_ALL_IMAGES, true));
417+
.setMaskAllImages(readBool(metadata, logger, REPLAYS_MASK_ALL_IMAGES, true));
418418
}
419419

420420
options

sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt

+7-7
Original file line numberDiff line numberDiff line change
@@ -1465,29 +1465,29 @@ class ManifestMetadataReaderTest {
14651465
}
14661466

14671467
@Test
1468-
fun `applyMetadata reads session replay redact flags to options`() {
1468+
fun `applyMetadata reads session replay mask flags to options`() {
14691469
// Arrange
1470-
val bundle = bundleOf(ManifestMetadataReader.REPLAYS_REDACT_ALL_TEXT to false, ManifestMetadataReader.REPLAYS_REDACT_ALL_IMAGES to false)
1470+
val bundle = bundleOf(ManifestMetadataReader.REPLAYS_MASK_ALL_TEXT to false, ManifestMetadataReader.REPLAYS_MASK_ALL_IMAGES to false)
14711471
val context = fixture.getContext(metaData = bundle)
14721472

14731473
// Act
14741474
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
14751475

14761476
// Assert
1477-
assertTrue(fixture.options.experimental.sessionReplay.ignoreViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
1478-
assertTrue(fixture.options.experimental.sessionReplay.ignoreViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
1477+
assertTrue(fixture.options.experimental.sessionReplay.unmaskViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
1478+
assertTrue(fixture.options.experimental.sessionReplay.unmaskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
14791479
}
14801480

14811481
@Test
1482-
fun `applyMetadata reads session replay redact flags to options and keeps default if not found`() {
1482+
fun `applyMetadata reads session replay mask flags to options and keeps default if not found`() {
14831483
// Arrange
14841484
val context = fixture.getContext()
14851485

14861486
// Act
14871487
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)
14881488

14891489
// Assert
1490-
assertTrue(fixture.options.experimental.sessionReplay.redactViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
1491-
assertTrue(fixture.options.experimental.sessionReplay.redactViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
1490+
assertTrue(fixture.options.experimental.sessionReplay.maskViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
1491+
assertTrue(fixture.options.experimental.sessionReplay.maskViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
14921492
}
14931493
}

sentry-android-core/src/test/java/io/sentry/android/core/SentryAndroidTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ class SentryAndroidTest {
440440
.untilTrue(asserted)
441441

442442
// assert that persisted values have changed
443-
options.executorService.close(5000L) // finalizes all enqueued persisting tasks
443+
options.executorService.close(10000L) // finalizes all enqueued persisting tasks
444444
assertEquals(
445445
"TestActivity",
446446
PersistingScopeObserver.read(options, TRANSACTION_FILENAME, String::class.java)

sentry-android-replay/api/sentry-android-replay.api

+9-9
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public final class io/sentry/android/replay/GeneratedVideo {
2929
}
3030

3131
public final class io/sentry/android/replay/ModifierExtensionsKt {
32-
public static final fun sentryReplayIgnore (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
33-
public static final fun sentryReplayRedact (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
32+
public static final fun sentryReplayMask (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
33+
public static final fun sentryReplayUnmask (Landroidx/compose/ui/Modifier;)Landroidx/compose/ui/Modifier;
3434
}
3535

3636
public abstract interface class io/sentry/android/replay/Recorder : java/io/Closeable {
@@ -120,15 +120,15 @@ public final class io/sentry/android/replay/SentryReplayModifiers {
120120
}
121121

122122
public final class io/sentry/android/replay/SessionReplayOptionsKt {
123-
public static final fun getRedactAllImages (Lio/sentry/SentryReplayOptions;)Z
124-
public static final fun getRedactAllText (Lio/sentry/SentryReplayOptions;)Z
125-
public static final fun setRedactAllImages (Lio/sentry/SentryReplayOptions;Z)V
126-
public static final fun setRedactAllText (Lio/sentry/SentryReplayOptions;Z)V
123+
public static final fun getMaskAllImages (Lio/sentry/SentryReplayOptions;)Z
124+
public static final fun getMaskAllText (Lio/sentry/SentryReplayOptions;)Z
125+
public static final fun setMaskAllImages (Lio/sentry/SentryReplayOptions;Z)V
126+
public static final fun setMaskAllText (Lio/sentry/SentryReplayOptions;Z)V
127127
}
128128

129129
public final class io/sentry/android/replay/ViewExtensionsKt {
130-
public static final fun sentryReplayIgnore (Landroid/view/View;)V
131-
public static final fun sentryReplayRedact (Landroid/view/View;)V
130+
public static final fun sentryReplayMask (Landroid/view/View;)V
131+
public static final fun sentryReplayUnmask (Landroid/view/View;)V
132132
}
133133

134134
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 {
230230
public final fun getElevation ()F
231231
public final fun getHeight ()I
232232
public final fun getParent ()Lio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;
233-
public final fun getShouldRedact ()Z
233+
public final fun getShouldMask ()Z
234234
public final fun getVisibleRect ()Landroid/graphics/Rect;
235235
public final fun getWidth ()I
236236
public final fun getX ()F

sentry-android-replay/proguard-rules.pro

+10-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
# debugging stack traces.
33
-keepattributes SourceFile,LineNumberTable
44

5-
# Rules to detect Images/Icons and redact them
5+
# Rules to detect Images/Icons and mask them
66
-dontwarn androidx.compose.ui.graphics.painter.Painter
77
-keepnames class * extends androidx.compose.ui.graphics.painter.Painter
88
-keepclasseswithmembernames class * {
99
androidx.compose.ui.graphics.painter.Painter painter;
1010
}
11-
# Rules to detect Text colors and if they have Modifier.fillMaxWidth to later redact them
11+
# Rules to detect Text colors and if they have Modifier.fillMaxWidth to later mask them
1212
-dontwarn androidx.compose.ui.graphics.ColorProducer
1313
-dontwarn androidx.compose.foundation.layout.FillElement
1414
-keepnames class androidx.compose.foundation.layout.FillElement
@@ -18,3 +18,11 @@
1818
# Rules to detect a compose view to parse its hierarchy
1919
-dontwarn androidx.compose.ui.platform.AndroidComposeView
2020
-keepnames class androidx.compose.ui.platform.AndroidComposeView
21+
# Rules to detect a media player view to later mask it
22+
-dontwarn androidx.media3.ui.PlayerView
23+
-keepnames class androidx.media3.ui.PlayerView
24+
# Rules to detect a ExoPlayer view to later mask it
25+
-dontwarn com.google.android.exoplayer2.ui.PlayerView
26+
-keepnames class com.google.android.exoplayer2.ui.PlayerView
27+
-dontwarn com.google.android.exoplayer2.ui.StyledPlayerView
28+
-keepnames class com.google.android.exoplayer2.ui.StyledPlayerView

sentry-android-replay/src/main/java/io/sentry/android/replay/ModifierExtensions.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,18 @@ public object SentryReplayModifiers {
1212
)
1313
}
1414

15-
public fun Modifier.sentryReplayRedact(): Modifier {
15+
public fun Modifier.sentryReplayMask(): Modifier {
1616
return semantics(
1717
properties = {
18-
this[SentryPrivacy] = "redact"
18+
this[SentryPrivacy] = "mask"
1919
}
2020
)
2121
}
2222

23-
public fun Modifier.sentryReplayIgnore(): Modifier {
23+
public fun Modifier.sentryReplayUnmask(): Modifier {
2424
return semantics(
2525
properties = {
26-
this[SentryPrivacy] = "ignore"
26+
this[SentryPrivacy] = "unmask"
2727
}
2828
)
2929
}

sentry-android-replay/src/main/java/io/sentry/android/replay/ScreenshotRecorder.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ internal class ScreenshotRecorder(
124124
val viewHierarchy = ViewHierarchyNode.fromView(root, null, 0, options)
125125
root.traverse(viewHierarchy, options)
126126

127-
recorder.submitSafely(options, "screenshot_recorder.redact") {
127+
recorder.submitSafely(options, "screenshot_recorder.mask") {
128128
val canvas = Canvas(bitmap)
129129
canvas.setMatrix(prescaledMatrix)
130130
viewHierarchy.traverse { node ->
131-
if (node.shouldRedact && (node.width > 0 && node.height > 0)) {
131+
if (node.shouldMask && (node.width > 0 && node.height > 0)) {
132132
node.visibleRect ?: return@traverse false
133133

134134
// TODO: investigate why it returns true on RN when it shouldn't

sentry-android-replay/src/main/java/io/sentry/android/replay/SessionReplayOptions.kt

+9-9
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,30 @@ package io.sentry.android.replay
22

33
import io.sentry.SentryReplayOptions
44

5-
// since we don't have getters for redactAllText and redactAllImages, they won't be accessible as
5+
// since we don't have getters for maskAllText and maskAllimages, they won't be accessible as
66
// properties in Kotlin, therefore we create these extensions where a getter is dummy, but a setter
77
// delegates to the corresponding method in SentryReplayOptions
88

99
/**
10-
* Redact all text content. Draws a rectangle of text bounds with text color on top. By default
11-
* only views extending TextView are redacted.
10+
* Mask all text content. Draws a rectangle of text bounds with text color on top. By default
11+
* only views extending TextView are masked.
1212
*
1313
* <p>Default is enabled.
1414
*/
15-
var SentryReplayOptions.redactAllText: Boolean
15+
var SentryReplayOptions.maskAllText: Boolean
1616
@Deprecated("Getter is unsupported.", level = DeprecationLevel.ERROR)
1717
get() = error("Getter not supported")
18-
set(value) = setRedactAllText(value)
18+
set(value) = setMaskAllText(value)
1919

2020
/**
21-
* Redact all image content. Draws a rectangle of image bounds with image's dominant color on top.
21+
* Mask all image content. Draws a rectangle of image bounds with image's dominant color on top.
2222
* By default only views extending ImageView with BitmapDrawable or custom Drawable type are
23-
* redacted. ColorDrawable, InsetDrawable, VectorDrawable are all considered non-PII, as they come
23+
* masked. ColorDrawable, InsetDrawable, VectorDrawable are all considered non-PII, as they come
2424
* from the apk.
2525
*
2626
* <p>Default is enabled.
2727
*/
28-
var SentryReplayOptions.redactAllImages: Boolean
28+
var SentryReplayOptions.maskAllImages: Boolean
2929
@Deprecated("Getter is unsupported.", level = DeprecationLevel.ERROR)
3030
get() = error("Getter not supported")
31-
set(value) = setRedactAllImages(value)
31+
set(value) = setMaskAllImages(value)

sentry-android-replay/src/main/java/io/sentry/android/replay/ViewExtensions.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ package io.sentry.android.replay
33
import android.view.View
44

55
/**
6-
* Marks this view to be redacted in session replay.
6+
* Marks this view to be masked in session replay.
77
*/
8-
fun View.sentryReplayRedact() {
9-
setTag(R.id.sentry_privacy, "redact")
8+
fun View.sentryReplayMask() {
9+
setTag(R.id.sentry_privacy, "mask")
1010
}
1111

1212
/**
13-
* Marks this view to be ignored from redaction in session.
13+
* Marks this view to be unmasked in session replay.
1414
* All its content will be visible in the replay, use with caution.
1515
*/
16-
fun View.sentryReplayIgnore() {
17-
setTag(R.id.sentry_privacy, "ignore")
16+
fun View.sentryReplayUnmask() {
17+
setTag(R.id.sentry_privacy, "unmask")
1818
}

sentry-android-replay/src/main/java/io/sentry/android/replay/util/Nodes.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ internal class ComposeTextLayout(internal val layout: TextLayoutResult, private
3737
// TODO: probably most of the below we can do via bytecode instrumentation and speed up at runtime
3838

3939
/**
40-
* This method is necessary to redact images in Compose.
40+
* This method is necessary to mask images in Compose.
4141
*
4242
* We heuristically look up for classes that have a [Painter] modifier, usually they all have a
4343
* `Painter` string in their name, e.g. PainterElement, PainterModifierNodeElement or
@@ -71,9 +71,9 @@ internal fun LayoutNode.findPainter(): Painter? {
7171
* [androidx.compose.ui.graphics.painter.BrushPainter]
7272
*
7373
* In theory, [androidx.compose.ui.graphics.painter.BitmapPainter] can also come from local assets,
74-
* but it can as well come from a network resource, so we preemptively redact it.
74+
* but it can as well come from a network resource, so we preemptively mask it.
7575
*/
76-
internal fun Painter.isRedactable(): Boolean {
76+
internal fun Painter.isMaskable(): Boolean {
7777
val className = this::class.java.name
7878
return !className.contains("Vector") &&
7979
!className.contains("Color") &&
@@ -83,11 +83,11 @@ internal fun Painter.isRedactable(): Boolean {
8383
internal data class TextAttributes(val color: Color?, val hasFillModifier: Boolean)
8484

8585
/**
86-
* This method is necessary to redact text in Compose.
86+
* This method is necessary to mask text in Compose.
8787
*
8888
* We heuristically look up for classes that have a [Text] modifier, usually they all have a
8989
* `Text` string in their name, e.g. TextStringSimpleElement or TextAnnotatedStringElement. We then
90-
* get the color from the modifier, to be able to redact it with the correct color.
90+
* get the color from the modifier, to be able to mask it with the correct color.
9191
*
9292
* We also look up for classes that have a [Fill] modifier, usually they all have a `Fill` string in
9393
* their name, e.g. FillElement. This is necessary to workaround a Compose bug where single-line

sentry-android-replay/src/main/java/io/sentry/android/replay/util/Views.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ internal fun View.isVisibleToUser(): Pair<Boolean, Rect?> {
8888

8989
@SuppressLint("ObsoleteSdkInt")
9090
@TargetApi(21)
91-
internal fun Drawable?.isRedactable(): Boolean {
91+
internal fun Drawable?.isMaskable(): Boolean {
9292
// TODO: maybe find a way how to check if the drawable is coming from the apk or loaded from network
93-
// 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)
93+
// 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)
9494
return when (this) {
9595
is InsetDrawable, is ColorDrawable, is VectorDrawable, is GradientDrawable -> false
9696
is BitmapDrawable -> {

sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ internal class SimpleVideoEncoder(
136136
)
137137
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
138138
format.setFloat(MediaFormat.KEY_FRAME_RATE, muxerConfig.frameRate.toFloat())
139-
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
139+
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
140140

141141
format
142142
}

0 commit comments

Comments
 (0)