diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b88ea57..7c1bf397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Next +- recording: mask views with `contentDescription` setting and mask `WebView` if any masking is enabled ([#149](https://github.com/PostHog/posthog-android/pull/149)) + ## 3.4.2 - 2024-06-28 - chore: create ctor overloads for better Java DX ([#148](https://github.com/PostHog/posthog-android/pull/148)) diff --git a/USAGE.md b/USAGE.md index 5af9f7f4..d04c5c03 100644 --- a/USAGE.md +++ b/USAGE.md @@ -219,7 +219,7 @@ val config = PostHogAndroidConfig(apiKey).apply { } ``` -If you don't want to mask everything, you can disable the mask config above and mask specific views using the `ph-no-capture` tag. +If you don't want to mask everything, you can disable the mask config above and mask specific views using the `ph-no-capture` value in the [android:tag](https://developer.android.com/reference/android/view/View#attr_android:tag) or [android:contentDescription](https://developer.android.com/reference/android/view/View#attr_android:contentDescription).. ```xml , ) { - var parentMasked = false - if (view is TextView) { val viewText = view.text?.toString() var maskIt = false @@ -488,48 +494,50 @@ public class PostHogReplayIntegration( if (maskIt) { val rect = view.globalVisibleRect() maskableWidgets.add(rect) - parentMasked = true + return } } if (view is Spinner) { - val maskIt = view.shouldMaskSpinner() - - if (maskIt) { + if (view.shouldMaskSpinner()) { val rect = view.globalVisibleRect() maskableWidgets.add(rect) - parentMasked = true + return } } if (view is ImageView) { - val maskIt = view.shouldMaskImage() + if (view.shouldMaskImage()) { + val rect = view.globalVisibleRect() + maskableWidgets.add(rect) + return + } + } - if (maskIt) { + if (view is WebView) { + if (view.isAnyInputSensitive()) { val rect = view.globalVisibleRect() maskableWidgets.add(rect) - parentMasked = true + return } } // if a view parent of any type is tagged as non masking, mask it - if (!parentMasked && view.isNoCapture()) { + if (view.isNoCapture()) { val rect = view.globalVisibleRect() maskableWidgets.add(rect) - parentMasked = true + return } - if (!parentMasked) { - if (view is ViewGroup && view.childCount > 0) { - for (i in 0 until view.childCount) { - val viewChild = view.getChildAt(i) ?: continue - - if (!viewChild.isVisible()) { - continue - } + if (view is ViewGroup && view.childCount > 0) { + for (i in 0 until view.childCount) { + val viewChild = view.getChildAt(i) ?: continue - findMaskableWidgets(viewChild, maskableWidgets) + if (!viewChild.isVisible()) { + continue } + + findMaskableWidgets(viewChild, maskableWidgets) } } } @@ -609,7 +617,7 @@ public class PostHogReplayIntegration( } private fun Spinner.shouldMaskSpinner(): Boolean { - return isNoCapture(config.sessionReplayConfig.maskAllTextInputs) + return this.isTextInputSensitive() } private fun View.toWireframe(parentId: Int? = null): RRWireframe? { @@ -1117,7 +1125,8 @@ public class PostHogReplayIntegration( } private fun View.isNoCapture(maskInput: Boolean = false): Boolean { - return (tag as? String)?.lowercase()?.contains("ph-no-capture") == true || maskInput + return maskInput || (tag as? String)?.lowercase()?.contains(PH_NO_CAPTURE_LABEL) == true || + contentDescription?.toString()?.lowercase()?.contains(PH_NO_CAPTURE_LABEL) == true } private fun Drawable.copy(): Drawable? { @@ -1132,4 +1141,8 @@ public class PostHogReplayIntegration( private fun isSupported(): Boolean { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O } + + private companion object { + private const val PH_NO_CAPTURE_LABEL = "ph-no-capture" + } } diff --git a/posthog-android/src/main/java/com/posthog/android/replay/internal/NextDrawListener.kt b/posthog-android/src/main/java/com/posthog/android/replay/internal/NextDrawListener.kt index 4faa8a8d..bdde2b57 100644 --- a/posthog-android/src/main/java/com/posthog/android/replay/internal/NextDrawListener.kt +++ b/posthog-android/src/main/java/com/posthog/android/replay/internal/NextDrawListener.kt @@ -28,7 +28,7 @@ internal class NextDrawListener( } } - companion object { + internal companion object { // only call if onDecorViewReady internal fun View.onNextDraw( mainHandler: MainHandler,