Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR] Support Jetpack Compose redaction #3739

Merged
merged 16 commits into from
Oct 9, 2024
16 changes: 9 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@

- 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))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved this as it was wrongly added to the Fixes section

- `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: `<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
- `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")`
- 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

### Fixes

- Avoid stopping appStartProfiler after application creation ([#3630](https://github.com/getsentry/sentry-java/pull/3630))
- Session Replay: Correctly detect dominant color for `TextView`s with Spans ([#3682](https://github.com/getsentry/sentry-java/pull/3682))
- 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: `<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
- `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")`
- If you're using code obfuscation, adjust your proguard-rules accordingly, so your custom view class name is not minified
- Fix ensure Application Context is used even when SDK is initialized via Activity Context ([#3669](https://github.com/getsentry/sentry-java/pull/3669))

*Breaking changes*:
Expand Down
3 changes: 3 additions & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,11 @@ object Config {
val composeActivity = "androidx.activity:activity-compose:1.4.0"
val composeFoundation = "androidx.compose.foundation:foundation:$composeVersion"
val composeUi = "androidx.compose.ui:ui:$composeVersion"

val composeUiReplay = "androidx.compose.ui:ui:1.5.0" // Note: don't change without testing forwards compatibility
val composeFoundationLayout = "androidx.compose.foundation:foundation-layout:$composeVersion"
val composeMaterial = "androidx.compose.material3:material3:1.0.0-alpha13"
val composeCoil = "io.coil-kt:coil-compose:2.6.0"

val apolloKotlin = "com.apollographql.apollo3:apollo-runtime:3.8.2"

Expand Down
54 changes: 51 additions & 3 deletions sentry-android-replay/api/sentry-android-replay.api
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ public final class io/sentry/android/replay/BuildConfig {
}

public class io/sentry/android/replay/DefaultReplayBreadcrumbConverter : io/sentry/ReplayBreadcrumbConverter {
public static final field $stable I
public fun <init> ()V
public fun convert (Lio/sentry/Breadcrumb;)Lio/sentry/rrweb/RRWebEvent;
}

public final class io/sentry/android/replay/GeneratedVideo {
public static final field $stable I
public fun <init> (Ljava/io/File;IJ)V
public final fun component1 ()Ljava/io/File;
public final fun component2 ()I
Expand All @@ -26,6 +28,11 @@ public final class io/sentry/android/replay/GeneratedVideo {
public fun toString ()Ljava/lang/String;
}

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 abstract interface class io/sentry/android/replay/Recorder : java/io/Closeable {
public abstract fun pause ()V
public abstract fun resume ()V
Expand All @@ -34,6 +41,7 @@ public abstract interface class io/sentry/android/replay/Recorder : java/io/Clos
}

public final class io/sentry/android/replay/ReplayCache : java/io/Closeable {
public static final field $stable I
public static final field Companion Lio/sentry/android/replay/ReplayCache$Companion;
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/protocol/SentryId;Lio/sentry/android/replay/ScreenshotRecorderConfig;)V
public final fun addFrame (Ljava/io/File;JLjava/lang/String;)V
Expand All @@ -50,6 +58,7 @@ public final class io/sentry/android/replay/ReplayCache$Companion {
}

public final class io/sentry/android/replay/ReplayIntegration : android/content/ComponentCallbacks, io/sentry/Integration, io/sentry/ReplayController, io/sentry/android/replay/ScreenshotRecorderCallback, io/sentry/android/replay/gestures/TouchRecorderCallback, java/io/Closeable {
public static final field $stable I
public fun <init> (Landroid/content/Context;Lio/sentry/transport/ICurrentDateProvider;)V
public fun <init> (Landroid/content/Context;Lio/sentry/transport/ICurrentDateProvider;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
public synthetic fun <init> (Landroid/content/Context;Lio/sentry/transport/ICurrentDateProvider;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down Expand Up @@ -78,6 +87,7 @@ public abstract interface class io/sentry/android/replay/ScreenshotRecorderCallb
}

public final class io/sentry/android/replay/ScreenshotRecorderConfig {
public static final field $stable I
public static final field Companion Lio/sentry/android/replay/ScreenshotRecorderConfig$Companion;
public fun <init> (IIFFII)V
public final fun component1 ()I
Expand All @@ -103,6 +113,12 @@ public final class io/sentry/android/replay/ScreenshotRecorderConfig$Companion {
public final fun from (Landroid/content/Context;Lio/sentry/SentryReplayOptions;)Lio/sentry/android/replay/ScreenshotRecorderConfig;
}

public final class io/sentry/android/replay/SentryReplayModifiers {
public static final field $stable I
public static final field INSTANCE Lio/sentry/android/replay/SentryReplayModifiers;
public final fun getSentryPrivacy ()Landroidx/compose/ui/semantics/SemanticsPropertyKey;
}

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
Expand All @@ -116,12 +132,14 @@ public final class io/sentry/android/replay/ViewExtensionsKt {
}

public final class io/sentry/android/replay/gestures/GestureRecorder : io/sentry/android/replay/OnRootViewsChangedListener {
public static final field $stable I
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/android/replay/gestures/TouchRecorderCallback;)V
public fun onRootViewsChanged (Landroid/view/View;Z)V
public final fun stop ()V
}

public final class io/sentry/android/replay/gestures/ReplayGestureConverter {
public static final field $stable I
public fun <init> (Lio/sentry/transport/ICurrentDateProvider;)V
public final fun convert (Landroid/view/MotionEvent;Lio/sentry/android/replay/ScreenshotRecorderConfig;)Ljava/util/List;
}
Expand All @@ -130,6 +148,19 @@ public abstract interface class io/sentry/android/replay/gestures/TouchRecorderC
public abstract fun onTouchEvent (Landroid/view/MotionEvent;)V
}

public final class io/sentry/android/replay/util/AndroidTextLayout : io/sentry/android/replay/util/TextLayout {
public static final field $stable I
public fun <init> (Landroid/text/Layout;)V
public fun getDominantTextColor ()Ljava/lang/Integer;
public fun getEllipsisCount (I)I
public fun getLineBottom (I)I
public fun getLineCount ()I
public fun getLineStart (I)I
public fun getLineTop (I)I
public fun getLineVisibleEnd (I)I
public fun getPrimaryHorizontal (II)F
}

public class io/sentry/android/replay/util/FixedWindowCallback : android/view/Window$Callback {
public final field delegate Landroid/view/Window$Callback;
public fun <init> (Landroid/view/Window$Callback;)V
Expand Down Expand Up @@ -160,6 +191,17 @@ public class io/sentry/android/replay/util/FixedWindowCallback : android/view/Wi
public fun onWindowStartingActionMode (Landroid/view/ActionMode$Callback;I)Landroid/view/ActionMode;
}

public abstract interface class io/sentry/android/replay/util/TextLayout {
public abstract fun getDominantTextColor ()Ljava/lang/Integer;
public abstract fun getEllipsisCount (I)I
public abstract fun getLineBottom (I)I
public abstract fun getLineCount ()I
public abstract fun getLineStart (I)I
public abstract fun getLineTop (I)I
public abstract fun getLineVisibleEnd (I)I
public abstract fun getPrimaryHorizontal (II)F
}

public abstract interface class io/sentry/android/replay/video/SimpleFrameMuxer {
public abstract fun getVideoTime ()J
public abstract fun isStarted ()Z
Expand All @@ -169,6 +211,7 @@ public abstract interface class io/sentry/android/replay/video/SimpleFrameMuxer
}

public final class io/sentry/android/replay/video/SimpleMp4FrameMuxer : io/sentry/android/replay/video/SimpleFrameMuxer {
public static final field $stable I
public fun <init> (Ljava/lang/String;F)V
public fun getVideoTime ()J
public fun isStarted ()Z
Expand All @@ -178,6 +221,7 @@ public final class io/sentry/android/replay/video/SimpleMp4FrameMuxer : io/sentr
}

public abstract class io/sentry/android/replay/viewhierarchy/ViewHierarchyNode {
public static final field $stable I
public static final field Companion Lio/sentry/android/replay/viewhierarchy/ViewHierarchyNode$Companion;
public synthetic fun <init> (FFIIFILio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;ZZZLandroid/graphics/Rect;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (FFIIFILio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;ZZZLandroid/graphics/Rect;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand All @@ -195,6 +239,7 @@ public abstract class io/sentry/android/replay/viewhierarchy/ViewHierarchyNode {
public final fun isObscured (Lio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;)Z
public final fun isVisible ()Z
public final fun setChildren (Ljava/util/List;)V
public final fun setImportantForCaptureToAncestors (Z)V
public final fun setImportantForContentCapture (Z)V
public final fun traverse (Lkotlin/jvm/functions/Function1;)V
}
Expand All @@ -204,20 +249,23 @@ public final class io/sentry/android/replay/viewhierarchy/ViewHierarchyNode$Comp
}

public final class io/sentry/android/replay/viewhierarchy/ViewHierarchyNode$GenericViewHierarchyNode : io/sentry/android/replay/viewhierarchy/ViewHierarchyNode {
public static final field $stable I
public fun <init> (FFIIFILio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;ZZZLandroid/graphics/Rect;)V
public synthetic fun <init> (FFIIFILio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;ZZZLandroid/graphics/Rect;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}

public final class io/sentry/android/replay/viewhierarchy/ViewHierarchyNode$ImageViewHierarchyNode : io/sentry/android/replay/viewhierarchy/ViewHierarchyNode {
public static final field $stable I
public fun <init> (FFIIFILio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;ZZZLandroid/graphics/Rect;)V
public synthetic fun <init> (FFIIFILio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;ZZZLandroid/graphics/Rect;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}

public final class io/sentry/android/replay/viewhierarchy/ViewHierarchyNode$TextViewHierarchyNode : io/sentry/android/replay/viewhierarchy/ViewHierarchyNode {
public fun <init> (Landroid/text/Layout;Ljava/lang/Integer;IIFFIIFILio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;ZZZLandroid/graphics/Rect;)V
public synthetic fun <init> (Landroid/text/Layout;Ljava/lang/Integer;IIFFIIFILio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;ZZZLandroid/graphics/Rect;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public static final field $stable I
public fun <init> (Lio/sentry/android/replay/util/TextLayout;Ljava/lang/Integer;IIFFIIFILio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;ZZZLandroid/graphics/Rect;)V
public synthetic fun <init> (Lio/sentry/android/replay/util/TextLayout;Ljava/lang/Integer;IIFFIIFILio/sentry/android/replay/viewhierarchy/ViewHierarchyNode;ZZZLandroid/graphics/Rect;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getDominantColor ()Ljava/lang/Integer;
public final fun getLayout ()Landroid/text/Layout;
public final fun getLayout ()Lio/sentry/android/replay/util/TextLayout;
public final fun getPaddingLeft ()I
public final fun getPaddingTop ()I
}
Expand Down
24 changes: 23 additions & 1 deletion sentry-android-replay/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import io.gitlab.arturbosch.detekt.Detekt
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask

plugins {
id("com.android.library")
Expand All @@ -25,9 +26,19 @@ android {
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
}

buildFeatures {
compose = true
}

composeOptions {
kotlinCompilerExtensionVersion = Config.androidComposeCompilerVersion
}

buildTypes {
getByName("debug")
getByName("release")
getByName("release") {
consumerProguardFiles("proguard-rules.pro")
}
}

kotlinOptions {
Expand Down Expand Up @@ -65,6 +76,7 @@ kotlin {
dependencies {
api(projects.sentry)

compileOnly(Config.Libs.composeUiReplay)
implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))

// tests
Expand All @@ -77,9 +89,19 @@ dependencies {
testImplementation(Config.TestLibs.mockitoKotlin)
testImplementation(Config.TestLibs.mockitoInline)
testImplementation(Config.TestLibs.awaitility)
testImplementation(Config.Libs.composeActivity)
testImplementation(Config.Libs.composeUi)
testImplementation(Config.Libs.composeCoil)
testImplementation(Config.Libs.composeFoundation)
testImplementation(Config.Libs.composeFoundationLayout)
testImplementation(Config.Libs.composeMaterial)
}

tasks.withType<Detekt> {
// Target version of the generated JVM bytecode. It is used for type resolution.
jvmTarget = JavaVersion.VERSION_1_8.toString()
}

tasks.withType<KotlinCompilationTask<*>>().configureEach {
compilerOptions.freeCompilerArgs.add("-opt-in=androidx.compose.ui.ExperimentalComposeUiApi")
}
17 changes: 17 additions & 0 deletions sentry-android-replay/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# Uncomment this to preserve the line number information for
# debugging stack traces.
-keepattributes SourceFile,LineNumberTable

# Rules to detect Images/Icons and redact 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
-dontwarn androidx.compose.ui.graphics.ColorProducer
-dontwarn androidx.compose.foundation.layout.FillElement
-keepnames class androidx.compose.foundation.layout.FillElement
-keepclasseswithmembernames class * {
androidx.compose.ui.graphics.ColorProducer color;
}
# Rules to detect a compose view to parse its hierarchy
-dontwarn androidx.compose.ui.platform.AndroidComposeView
-keepnames class androidx.compose.ui.platform.AndroidComposeView
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.sentry.android.replay

import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.semantics
import io.sentry.android.replay.SentryReplayModifiers.SentryPrivacy

public object SentryReplayModifiers {
romtsn marked this conversation as resolved.
Show resolved Hide resolved
val SentryPrivacy = SemanticsPropertyKey<String>(
name = "SentryPrivacy",
mergePolicy = { parentValue, _ -> parentValue }
)
}

public fun Modifier.sentryReplayRedact(): Modifier {
return semantics(
properties = {
this[SentryPrivacy] = "redact"
}
)
}

public fun Modifier.sentryReplayIgnore(): Modifier {
return semantics(
properties = {
this[SentryPrivacy] = "ignore"
}
)
}
Loading
Loading