Skip to content

Commit

Permalink
Merge branch 'main' into feat/support-feedback-sentry-item-type
Browse files Browse the repository at this point in the history
  • Loading branch information
denrase committed Sep 17, 2024
2 parents b1d2f78 + 6368d4f commit 598b002
Show file tree
Hide file tree
Showing 23 changed files with 488 additions and 83 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/agp-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@fd87365911aa12c016c307ea21313f351dc53551 # pin@v3
uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3
with:
gradle-home-cache-cleanup: true

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@fd87365911aa12c016c307ea21313f351dc53551 # pin@v3
uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3
with:
gradle-home-cache-cleanup: true

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ jobs:
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@fd87365911aa12c016c307ea21313f351dc53551 # pin@v3
uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3
with:
gradle-home-cache-cleanup: true

- name: Initialize CodeQL
uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # pin@v2
uses: github/codeql-action/init@8214744c546c1e5c8f03dde8fab3a7353211988d # pin@v2
with:
languages: ${{ matrix.language }}

Expand All @@ -55,4 +55,4 @@ jobs:
./gradlew buildForCodeQL
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # pin@v2
uses: github/codeql-action/analyze@8214744c546c1e5c8f03dde8fab3a7353211988d # pin@v2
2 changes: 1 addition & 1 deletion .github/workflows/enforce-license-compliance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@fd87365911aa12c016c307ea21313f351dc53551 # pin@v3
uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3
with:
gradle-home-cache-cleanup: true

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/generate-javadocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@fd87365911aa12c016c307ea21313f351dc53551 # pin@v3
uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3
with:
gradle-home-cache-cleanup: true

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/integration-tests-benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@fd87365911aa12c016c307ea21313f351dc53551 # pin@v3
uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3
with:
gradle-home-cache-cleanup: true

Expand Down Expand Up @@ -86,7 +86,7 @@ jobs:
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@fd87365911aa12c016c307ea21313f351dc53551 # pin@v3
uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3
with:
gradle-home-cache-cleanup: true

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration-tests-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@fd87365911aa12c016c307ea21313f351dc53551 # pin@v3
uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3
with:
gradle-home-cache-cleanup: true

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@fd87365911aa12c016c307ea21313f351dc53551 # pin@v3
uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3
with:
gradle-home-cache-cleanup: true

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/system-tests-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@fd87365911aa12c016c307ea21313f351dc53551 # pin@v3
uses: gradle/actions/setup-gradle@0d30c9111cf47a838eb69c06d13f3f51ab2ed76f # pin@v3
with:
gradle-home-cache-cleanup: true

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@

- 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

*Breaking changes*:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,22 +409,12 @@ static void applyMetadata(
options
.getExperimental()
.getSessionReplay()
.setRedactAllText(
readBool(
metadata,
logger,
REPLAYS_REDACT_ALL_TEXT,
options.getExperimental().getSessionReplay().getRedactAllText()));
.setRedactAllText(readBool(metadata, logger, REPLAYS_REDACT_ALL_TEXT, true));

options
.getExperimental()
.getSessionReplay()
.setRedactAllImages(
readBool(
metadata,
logger,
REPLAYS_REDACT_ALL_IMAGES,
options.getExperimental().getSessionReplay().getRedactAllImages()));
.setRedactAllImages(readBool(metadata, logger, REPLAYS_REDACT_ALL_IMAGES, true));
}

options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.core.os.bundleOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.ILogger
import io.sentry.SentryLevel
import io.sentry.SentryReplayOptions
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
Expand Down Expand Up @@ -1473,8 +1474,8 @@ class ManifestMetadataReaderTest {
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertFalse(fixture.options.experimental.sessionReplay.redactAllImages)
assertFalse(fixture.options.experimental.sessionReplay.redactAllText)
assertTrue(fixture.options.experimental.sessionReplay.ignoreViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
assertTrue(fixture.options.experimental.sessionReplay.ignoreViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
}

@Test
Expand All @@ -1486,7 +1487,7 @@ class ManifestMetadataReaderTest {
ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider)

// Assert
assertTrue(fixture.options.experimental.sessionReplay.redactAllImages)
assertTrue(fixture.options.experimental.sessionReplay.redactAllText)
assertTrue(fixture.options.experimental.sessionReplay.redactViewClasses.contains(SentryReplayOptions.IMAGE_VIEW_CLASS_NAME))
assertTrue(fixture.options.experimental.sessionReplay.redactViewClasses.contains(SentryReplayOptions.TEXT_VIEW_CLASS_NAME))
}
}
12 changes: 12 additions & 0 deletions sentry-android-replay/api/sentry-android-replay.api
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ 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/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 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 final class io/sentry/android/replay/gestures/GestureRecorder : io/sentry/android/replay/OnRootViewsChangedListener {
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/android/replay/gestures/TouchRecorderCallback;)V
public fun onRootViewsChanged (Landroid/view/View;Z)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public class ReplayCache(
if (replayCacheDir == null || bitmap.isRecycled) {
return
}
replayCacheDir?.mkdirs()

val screenshot = File(replayCacheDir, "$frameTimestamp.jpg").also {
it.createNewFile()
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.
*
* <p>Default is enabled.
*/
var SentryReplayOptions.redactAllImages: Boolean
@Deprecated("Getter is unsupported.", level = DeprecationLevel.ERROR)
get() = error("Getter not supported")
set(value) = setRedactAllImages(value)
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<OnRootViewsChangedListener> = object : CopyOnWriteArrayList<OnRootViewsChangedListener>() {
override fun add(element: OnRootViewsChangedListener?): Boolean {
Expand Down Expand Up @@ -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) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String>): 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,
Expand All @@ -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,
Expand All @@ -261,7 +294,7 @@ sealed class ViewHierarchyNode(
)
}

view is ImageView && options.experimental.sessionReplay.redactAllImages -> {
is ImageView -> {
parent.setImportantForCaptureToAncestors(true)
return ImageViewHierarchyNode(
x = view.x,
Expand All @@ -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
)
}
Expand All @@ -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
Expand Down
4 changes: 0 additions & 4 deletions sentry-android-replay/src/main/res/public.xml

This file was deleted.

5 changes: 5 additions & 0 deletions sentry-android-replay/src/main/res/values/public.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<public name="sentry_privacy" type="id"/>
<item name="sentry_privacy" type="id" format="string"/>
</resources>
Loading

0 comments on commit 598b002

Please sign in to comment.