Skip to content

Commit

Permalink
RUM-6218: Add privacy override for hidden views
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanmos committed Sep 26, 2024
1 parent 7ba01ef commit f466427
Show file tree
Hide file tree
Showing 18 changed files with 653 additions and 36 deletions.
1 change: 1 addition & 0 deletions detekt_custom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ datadog:
- "java.util.Queue.isEmpty()"
- "java.util.Queue.isNotEmpty()"
- "java.util.Queue.poll()"
- "java.util.concurrent.ConcurrentHashMap.clear()"
- "java.util.concurrent.ConcurrentHashMap.constructor()"
- "java.util.concurrent.ConcurrentLinkedQueue.constructor()"
- "java.util.concurrent.ConcurrentLinkedQueue.isEmpty()"
Expand Down
11 changes: 8 additions & 3 deletions features/dd-sdk-android-session-replay/api/apiSurface
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
interface com.datadog.android.sessionreplay.ExtensionSupport
fun getCustomViewMappers(): List<MapperTypeWrapper<*>>
fun getOptionSelectorDetectors(): List<com.datadog.android.sessionreplay.recorder.OptionSelectorDetector>
enum com.datadog.android.sessionreplay.ImagePrivacy
enum com.datadog.android.sessionreplay.ImagePrivacy : PrivacyLevel
- MASK_NONE
- MASK_LARGE_ONLY
- MASK_ALL
data class com.datadog.android.sessionreplay.MapperTypeWrapper<T: android.view.View>
constructor(Class<T>, com.datadog.android.sessionreplay.recorder.mapper.WireframeMapper<T>)
fun supportsView(android.view.View): Boolean
fun getUnsafeMapper(): com.datadog.android.sessionreplay.recorder.mapper.WireframeMapper<android.view.View>
interface com.datadog.android.sessionreplay.PrivacyLevel
fun android.view.View.ddSessionReplayOverrideTextAndInputPrivacy(TextAndInputPrivacy?)
fun android.view.View.ddSessionReplayOverrideTouchPrivacy(TouchPrivacy?)
fun android.view.View.ddSessionReplayOverrideImagePrivacy(ImagePrivacy?)
fun android.view.View.ddSessionReplayOverrideHidden(Boolean)
object com.datadog.android.sessionreplay.SessionReplay
fun enable(SessionReplayConfiguration, com.datadog.android.api.SdkCore = Datadog.getInstance())
fun startRecording(com.datadog.android.api.SdkCore = Datadog.getInstance())
Expand All @@ -28,11 +33,11 @@ enum com.datadog.android.sessionreplay.SessionReplayPrivacy
- ALLOW
- MASK
- MASK_USER_INPUT
enum com.datadog.android.sessionreplay.TextAndInputPrivacy
enum com.datadog.android.sessionreplay.TextAndInputPrivacy : PrivacyLevel
- MASK_SENSITIVE_INPUTS
- MASK_ALL_INPUTS
- MASK_ALL
enum com.datadog.android.sessionreplay.TouchPrivacy
enum com.datadog.android.sessionreplay.TouchPrivacy : PrivacyLevel
- SHOW
- HIDE
data class com.datadog.android.sessionreplay.recorder.MappingContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ public abstract interface class com/datadog/android/sessionreplay/ExtensionSuppo
public abstract fun getOptionSelectorDetectors ()Ljava/util/List;
}

public final class com/datadog/android/sessionreplay/ImagePrivacy : java/lang/Enum {
public final class com/datadog/android/sessionreplay/ImagePrivacy : java/lang/Enum, com/datadog/android/sessionreplay/PrivacyLevel {
public static final field MASK_ALL Lcom/datadog/android/sessionreplay/ImagePrivacy;
public static final field MASK_LARGE_ONLY Lcom/datadog/android/sessionreplay/ImagePrivacy;
public static final field MASK_NONE Lcom/datadog/android/sessionreplay/ImagePrivacy;
Expand All @@ -22,6 +22,16 @@ public final class com/datadog/android/sessionreplay/MapperTypeWrapper {
public fun toString ()Ljava/lang/String;
}

public abstract interface class com/datadog/android/sessionreplay/PrivacyLevel {
}

public final class com/datadog/android/sessionreplay/PrivacyOverrideExtensionsKt {
public static final fun ddSessionReplayOverrideHidden (Landroid/view/View;Z)V
public static final fun ddSessionReplayOverrideImagePrivacy (Landroid/view/View;Lcom/datadog/android/sessionreplay/ImagePrivacy;)V
public static final fun ddSessionReplayOverrideTextAndInputPrivacy (Landroid/view/View;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;)V
public static final fun ddSessionReplayOverrideTouchPrivacy (Landroid/view/View;Lcom/datadog/android/sessionreplay/TouchPrivacy;)V
}

public final class com/datadog/android/sessionreplay/SessionReplay {
public static final field INSTANCE Lcom/datadog/android/sessionreplay/SessionReplay;
public static final fun enable (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;)V
Expand Down Expand Up @@ -61,15 +71,15 @@ public final class com/datadog/android/sessionreplay/SessionReplayPrivacy : java
public static fun values ()[Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;
}

public final class com/datadog/android/sessionreplay/TextAndInputPrivacy : java/lang/Enum {
public final class com/datadog/android/sessionreplay/TextAndInputPrivacy : java/lang/Enum, com/datadog/android/sessionreplay/PrivacyLevel {
public static final field MASK_ALL Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;
public static final field MASK_ALL_INPUTS Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;
public static final field MASK_SENSITIVE_INPUTS Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;
public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;
public static fun values ()[Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;
}

public final class com/datadog/android/sessionreplay/TouchPrivacy : java/lang/Enum {
public final class com/datadog/android/sessionreplay/TouchPrivacy : java/lang/Enum, com/datadog/android/sessionreplay/PrivacyLevel {
public static final field HIDE Lcom/datadog/android/sessionreplay/TouchPrivacy;
public static final field SHOW Lcom/datadog/android/sessionreplay/TouchPrivacy;
public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/sessionreplay/TouchPrivacy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ package com.datadog.android.sessionreplay
* @see ImagePrivacy.MASK_LARGE_ONLY
* @see ImagePrivacy.MASK_ALL
*/
enum class ImagePrivacy {
enum class ImagePrivacy : PrivacyLevel {
/**
* All images will be recorded, including those downloaded from the Internet during app runtime.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.sessionreplay

/**
* Common interface for Session Replay privacy levels.
* Privacy levels determine the degree of masking applied to sessions.
*/
interface PrivacyLevel
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.sessionreplay

import android.view.View
import com.datadog.android.sessionreplay.internal.PrivacyOverrideManager

/**
* Allows adding and removing `TextAndInputPrivacy` for a specific view in Session Replay.
* Once added, this override will impact the privacy of the view and it's children
* taking priority over the global privacy level
*
* @param level the TextAndInputPrivacy to set for the view,
* or pass null to remove the override
*/
fun View.ddSessionReplayOverrideTextAndInputPrivacy(level: TextAndInputPrivacy?) {
if (id == View.NO_ID) return

if (level == null) {
PrivacyOverrideManager.removeTextAndInputPrivacyOverride(id)
} else {
PrivacyOverrideManager.addPrivacyOverride(id, level)
}
}

/**
* Allows adding and removing `TouchPrivacy` for a specific view in Session Replay.
* Once added, this override will impact the privacy of the view and it's children
* taking priority over the global privacy level
*
* @param level the TouchPrivacy to set for the view,
* or pass null to remove the override
*/
fun View.ddSessionReplayOverrideTouchPrivacy(level: TouchPrivacy?) {
if (id == View.NO_ID) return

if (level == null) {
PrivacyOverrideManager.removeTouchPrivacyOverride(id)
} else {
PrivacyOverrideManager.addPrivacyOverride(id, level)
}
}

/**
* Allows adding and removing `ImagePrivacy` for a specific view in Session Replay.
* Once added, this override will impact the privacy of the view and it's children
* taking priority over the global privacy level
*
* @param level the ImagePrivacy to set for the view,
* or pass null to remove the override
*/
fun View.ddSessionReplayOverrideImagePrivacy(level: ImagePrivacy?) {
if (id == View.NO_ID) return

if (level == null) {
PrivacyOverrideManager.removeImagePrivacyOverride(id)
} else {
PrivacyOverrideManager.addPrivacyOverride(id, level)
}
}

/**
* Allows setting a view to be "hidden" in the hierarchy in Session Replay.
* When hidden the view will be replaced with an opaque square in the replay and
* no attempt will be made to record it's children.
*
* @param hide pass `true` to hide the view, or `false` to remove the override
*/
fun View.ddSessionReplayOverrideHidden(hide: Boolean) {
if (id == View.NO_ID) return

if (hide) {
PrivacyOverrideManager.setViewHidden(id)
} else {
PrivacyOverrideManager.setViewNotHidden(id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ package com.datadog.android.sessionreplay
* @see TextAndInputPrivacy.MASK_ALL_INPUTS
* @see TextAndInputPrivacy.MASK_ALL
*/
enum class TextAndInputPrivacy {
enum class TextAndInputPrivacy : PrivacyLevel {

/**
* All text and inputs considered sensitive will be masked.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package com.datadog.android.sessionreplay
* @see TouchPrivacy.SHOW
* @see TouchPrivacy.HIDE
*/
enum class TouchPrivacy {
enum class TouchPrivacy : PrivacyLevel {
/**
* All touch interactions will be recorded.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.sessionreplay.internal

import com.datadog.android.sessionreplay.ImagePrivacy
import com.datadog.android.sessionreplay.TextAndInputPrivacy
import com.datadog.android.sessionreplay.TouchPrivacy

internal data class PrivacyOverride(
val imagePrivacy: ImagePrivacy? = null,
val touchPrivacy: TouchPrivacy? = null,
val textAndInputPrivacy: TextAndInputPrivacy? = null,
val hiddenPrivacy: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.sessionreplay.internal

import com.datadog.android.sessionreplay.ImagePrivacy
import com.datadog.android.sessionreplay.PrivacyLevel
import com.datadog.android.sessionreplay.TextAndInputPrivacy
import com.datadog.android.sessionreplay.TouchPrivacy
import java.util.concurrent.ConcurrentHashMap

internal object PrivacyOverrideManager {
private val overridesMap = ConcurrentHashMap<Int, PrivacyOverride>()

internal fun addPrivacyOverride(id: Int, level: PrivacyLevel?) {
val existingPrivacy = overridesMap[id] ?: PrivacyOverride()

overridesMap[id] =
when (level) {
is ImagePrivacy -> existingPrivacy.copy(
imagePrivacy = level
)

is TextAndInputPrivacy -> existingPrivacy.copy(
textAndInputPrivacy = level
)

is TouchPrivacy -> existingPrivacy.copy(
touchPrivacy = level
)

else -> return
}
}

internal fun setViewHidden(id: Int) {
val existingPrivacy = overridesMap[id] ?: PrivacyOverride()
overridesMap[id] = existingPrivacy.copy(
hiddenPrivacy = true
)
}

internal fun removeTextAndInputPrivacyOverride(id: Int) {
val existingPrivacy = overridesMap[id] ?: return

overridesMap[id] = existingPrivacy.copy(
textAndInputPrivacy = null
)
}

internal fun removeTouchPrivacyOverride(id: Int) {
val existingPrivacy = overridesMap[id] ?: return

overridesMap[id] = existingPrivacy.copy(
touchPrivacy = null
)
}

internal fun removeImagePrivacyOverride(id: Int) {
val existingPrivacy = overridesMap[id] ?: return

overridesMap[id] = existingPrivacy.copy(
imagePrivacy = null
)
}

internal fun setViewNotHidden(id: Int) {
val existingPrivacy = overridesMap[id] ?: return

overridesMap[id] = existingPrivacy.copy(
hiddenPrivacy = false
)
}

internal fun getPrivacyOverrides(id: Int): PrivacyOverride? {
return overridesMap[id]
}

internal fun clearOverrides() {
overridesMap.clear()
}

internal fun isHidden(id: Int): Boolean {
return overridesMap[id]?.hiddenPrivacy == true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.datadog.android.sessionreplay.internal.processor.RecordedDataProcesso
import com.datadog.android.sessionreplay.internal.processor.RumContextDataHandler
import com.datadog.android.sessionreplay.internal.recorder.callback.OnWindowRefreshedCallback
import com.datadog.android.sessionreplay.internal.recorder.mapper.DecorViewMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.HiddenViewMapper
import com.datadog.android.sessionreplay.internal.recorder.mapper.ViewWireframeMapper
import com.datadog.android.sessionreplay.internal.recorder.resources.BitmapCachesManager
import com.datadog.android.sessionreplay.internal.recorder.resources.BitmapPool
Expand Down Expand Up @@ -172,6 +173,10 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder {
mappers = mappers,
defaultViewMapper = defaultVWM,
decorViewMapper = DecorViewMapper(defaultVWM, viewIdentifierResolver),
hiddenViewMapper = HiddenViewMapper(
viewBoundsResolver = viewBoundsResolver,
viewIdentifierResolver = viewIdentifierResolver
),
viewUtilsInternal = ViewUtilsInternal(),
internalLogger = internalLogger
),
Expand Down
Loading

0 comments on commit f466427

Please sign in to comment.