From 5a400b503654e307f344c52f53a049aa4874a202 Mon Sep 17 00:00:00 2001 From: Subhradeep Date: Wed, 16 Oct 2024 15:58:45 +0530 Subject: [PATCH 1/8] add replay masking to jetpack compose views --- buildSrc/src/main/java/PosthogBuildConfig.kt | 2 +- posthog-android/build.gradle.kts | 1 + .../android/replay/PostHogMaskModifier.kt | 17 ++++++ .../replay/PostHogReplayIntegration.kt | 60 +++++++++++++++++++ .../posthog/android/sample/MainActivity.kt | 13 ++-- 5 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt diff --git a/buildSrc/src/main/java/PosthogBuildConfig.kt b/buildSrc/src/main/java/PosthogBuildConfig.kt index cc66e66c..89eeaa60 100644 --- a/buildSrc/src/main/java/PosthogBuildConfig.kt +++ b/buildSrc/src/main/java/PosthogBuildConfig.kt @@ -14,7 +14,7 @@ object PosthogBuildConfig { } object Android { - val COMPILE_SDK = 33 + val COMPILE_SDK = 34 // when changing this, remember to check the ANIMAL_SNIFFER_SDK_VERSION // Session Replay (addOnFrameMetricsAvailableListener requires API 26) diff --git a/posthog-android/build.gradle.kts b/posthog-android/build.gradle.kts index 1a6dda83..c84eb1a9 100644 --- a/posthog-android/build.gradle.kts +++ b/posthog-android/build.gradle.kts @@ -89,6 +89,7 @@ dependencies { implementation("androidx.lifecycle:lifecycle-common-java8:${PosthogBuildConfig.Dependencies.LIFECYCLE}") implementation("androidx.core:core:${PosthogBuildConfig.Dependencies.ANDROIDX_CORE}") implementation("com.squareup.curtains:curtains:${PosthogBuildConfig.Dependencies.CURTAINS}") + implementation("androidx.compose.ui:ui-android:1.7.3") // compatibility signature("org.codehaus.mojo.signature:java18:${PosthogBuildConfig.Plugins.SIGNATURE_JAVA18}@signature") diff --git a/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt b/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt new file mode 100644 index 00000000..2cabab1c --- /dev/null +++ b/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt @@ -0,0 +1,17 @@ +package com.posthog.android.replay + +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.SemanticsPropertyKey +import androidx.compose.ui.semantics.semantics + +public object PostHogMaskModifier { + internal val PostHogReplayMask = SemanticsPropertyKey("ph-no-capture") + + public fun Modifier.postHogMaskReplay(): Modifier { + return semantics( + properties = { + this[PostHogReplayMask] = true + }, + ) + } +} diff --git a/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt b/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt index 70488127..6661c84d 100644 --- a/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt @@ -46,6 +46,10 @@ import android.widget.RatingBar import android.widget.Spinner import android.widget.Switch import android.widget.TextView +import androidx.compose.ui.node.RootForTest +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.semantics.getAllSemanticsNodes +import androidx.compose.ui.semantics.getOrNull import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import com.posthog.PostHog @@ -55,6 +59,7 @@ import com.posthog.android.internal.MainHandler import com.posthog.android.internal.densityValue import com.posthog.android.internal.displayMetrics import com.posthog.android.internal.screenSize +import com.posthog.android.replay.PostHogMaskModifier.PostHogReplayMask import com.posthog.android.replay.internal.NextDrawListener.Companion.onNextDraw import com.posthog.android.replay.internal.ViewTreeSnapshotStatus import com.posthog.android.replay.internal.isAliveAndAttachedToWindow @@ -553,6 +558,10 @@ public class PostHogReplayIntegration( } } + if (view.isComposeView()) { + findMaskableComposeRects(view, maskableWidgets) + } + // if a view parent of any type is tagged as non masking, mask it if (view.isNoCapture()) { val rect = view.globalVisibleRect() @@ -573,6 +582,55 @@ public class PostHogReplayIntegration( } } + private fun findMaskableComposeRects( + view: View, + rectsToMask: MutableList, + ) { + val semanticsOwner = + (view as? RootForTest)?.semanticsOwner ?: run { + config.logger.log("View is not a RootForTest: $view") + return + } + val semanticsNodes = semanticsOwner.getAllSemanticsNodes(true) + + semanticsNodes.forEach { node -> + val isTextInput = node.config.contains(SemanticsProperties.EditableText) + val isPassword = node.config.contains(SemanticsProperties.Password) + val isImage = node.config.contains(SemanticsProperties.ContentDescription) + val shouldMaskAnyway = node.config.getOrNull(PostHogReplayMask) == true + + if (!shouldMaskAnyway) { + if (isTextInput && (config.sessionReplayConfig.maskAllTextInputs || isPassword)) { + rectsToMask.add(node.boundsInWindow.toRect()) + } + + if (isImage && config.sessionReplayConfig.maskAllImages) { + rectsToMask.add(node.boundsInWindow.toRect()) + } + } else { + rectsToMask.add(node.boundsInWindow.toRect()) + } + } + } + + private fun androidx.compose.ui.geometry.Rect.toRect(): Rect { + return Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt()) + } + + private fun View.isComposeView(): Boolean { + return isComposeAvailable && this.javaClass.name.contains(ANDROID_COMPOSE_VIEW) + } + + private val isComposeAvailable by lazy(LazyThreadSafetyMode.PUBLICATION) { + try { + Class.forName(ANDROID_COMPOSE_VIEW_CLASS_NAME) + true + } catch (e: Throwable) { + config.logger.log("Compose not available: $e.") + false + } + } + // PixelCopy is only API >= 24 but this is already protected by the isSupported method @SuppressLint("NewApi") private fun View.toScreenshotWireframe(window: Window): RRWireframe? { @@ -1175,5 +1233,7 @@ public class PostHogReplayIntegration( private companion object { private const val PH_NO_CAPTURE_LABEL = "ph-no-capture" + private const val ANDROID_COMPOSE_VIEW_CLASS_NAME = "androidx.compose.ui.platform.AndroidComposeView" + private const val ANDROID_COMPOSE_VIEW = "AndroidComposeView" } } diff --git a/posthog-samples/posthog-android-sample/src/main/java/com/posthog/android/sample/MainActivity.kt b/posthog-samples/posthog-android-sample/src/main/java/com/posthog/android/sample/MainActivity.kt index a882ebee..828ec3fb 100644 --- a/posthog-samples/posthog-android-sample/src/main/java/com/posthog/android/sample/MainActivity.kt +++ b/posthog-samples/posthog-android-sample/src/main/java/com/posthog/android/sample/MainActivity.kt @@ -4,10 +4,11 @@ import android.annotation.SuppressLint import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -44,12 +45,12 @@ fun greeting( ) { var text by remember { mutableStateOf("Hello $name!") } - ClickableText( + Text( text = AnnotatedString(text), - modifier = modifier, - onClick = { - text = "Clicked!" - }, + modifier = + modifier.clickable { + text = "Clicked!" + }, ) } From 72d5908b0dd45ec015488c9f6a636f2f7e6fd522 Mon Sep 17 00:00:00 2001 From: Subhradeep Date: Wed, 16 Oct 2024 16:07:47 +0530 Subject: [PATCH 2/8] add doc for modifier --- .../java/com/posthog/android/replay/PostHogMaskModifier.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt b/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt index 2cabab1c..f87d507f 100644 --- a/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt +++ b/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt @@ -7,6 +7,9 @@ import androidx.compose.ui.semantics.semantics public object PostHogMaskModifier { internal val PostHogReplayMask = SemanticsPropertyKey("ph-no-capture") + /** + * Marks the element as not to be captured by PostHog Session Replay. + */ public fun Modifier.postHogMaskReplay(): Modifier { return semantics( properties = { From 138c63790d0509cde47527a2355f682aba82b903 Mon Sep 17 00:00:00 2001 From: Subhradeep Date: Sat, 19 Oct 2024 22:03:18 +0530 Subject: [PATCH 3/8] fixes --- buildSrc/src/main/java/PosthogBuildConfig.kt | 3 +- posthog-android/api/posthog-android.api | 9 ++ posthog-android/build.gradle.kts | 2 +- posthog-android/lint-baseline.xml | 10 +- .../android/replay/PostHogMaskModifier.kt | 12 +- .../replay/PostHogReplayIntegration.kt | 132 +++++++++--------- 6 files changed, 93 insertions(+), 75 deletions(-) diff --git a/buildSrc/src/main/java/PosthogBuildConfig.kt b/buildSrc/src/main/java/PosthogBuildConfig.kt index 89eeaa60..9a9d7172 100644 --- a/buildSrc/src/main/java/PosthogBuildConfig.kt +++ b/buildSrc/src/main/java/PosthogBuildConfig.kt @@ -14,7 +14,7 @@ object PosthogBuildConfig { } object Android { - val COMPILE_SDK = 34 + val COMPILE_SDK = 33 // when changing this, remember to check the ANIMAL_SNIFFER_SDK_VERSION // Session Replay (addOnFrameMetricsAvailableListener requires API 26) @@ -55,6 +55,7 @@ object PosthogBuildConfig { val OKHTTP = "4.11.0" val CURTAINS = "1.2.5" val ANDROIDX_CORE = "1.5.0" + val ANDROIDX_COMPOSE = "1.0.0" // tests val ANDROIDX_JUNIT = "1.1.5" diff --git a/posthog-android/api/posthog-android.api b/posthog-android/api/posthog-android.api index 0e0d24e9..aab34421 100644 --- a/posthog-android/api/posthog-android.api +++ b/posthog-android/api/posthog-android.api @@ -49,7 +49,16 @@ public abstract interface class com/posthog/android/replay/PostHogDrawableConver public abstract fun convert (Landroid/graphics/drawable/Drawable;)Landroid/graphics/Bitmap; } +public final class com/posthog/android/replay/PostHogMaskModifier { + public static final field INSTANCE Lcom/posthog/android/replay/PostHogMaskModifier; + public final fun postHogMask (Landroidx/compose/ui/Modifier;Z)Landroidx/compose/ui/Modifier; + public static synthetic fun postHogMask$default (Lcom/posthog/android/replay/PostHogMaskModifier;Landroidx/compose/ui/Modifier;ZILjava/lang/Object;)Landroidx/compose/ui/Modifier; +} + public final class com/posthog/android/replay/PostHogReplayIntegration : com/posthog/PostHogIntegration { + public static final field ANDROID_COMPOSE_VIEW Ljava/lang/String; + public static final field ANDROID_COMPOSE_VIEW_CLASS_NAME Ljava/lang/String; + public static final field PH_NO_CAPTURE_LABEL Ljava/lang/String; public fun (Landroid/content/Context;Lcom/posthog/android/PostHogAndroidConfig;Lcom/posthog/android/internal/MainHandler;)V public fun install ()V public fun uninstall ()V diff --git a/posthog-android/build.gradle.kts b/posthog-android/build.gradle.kts index c84eb1a9..dc081a1b 100644 --- a/posthog-android/build.gradle.kts +++ b/posthog-android/build.gradle.kts @@ -88,8 +88,8 @@ dependencies { implementation("androidx.lifecycle:lifecycle-process:${PosthogBuildConfig.Dependencies.LIFECYCLE}") implementation("androidx.lifecycle:lifecycle-common-java8:${PosthogBuildConfig.Dependencies.LIFECYCLE}") implementation("androidx.core:core:${PosthogBuildConfig.Dependencies.ANDROIDX_CORE}") + implementation("androidx.compose.ui:ui:${PosthogBuildConfig.Dependencies.ANDROIDX_COMPOSE}") implementation("com.squareup.curtains:curtains:${PosthogBuildConfig.Dependencies.CURTAINS}") - implementation("androidx.compose.ui:ui-android:1.7.3") // compatibility signature("org.codehaus.mojo.signature:java18:${PosthogBuildConfig.Plugins.SIGNATURE_JAVA18}@signature") diff --git a/posthog-android/lint-baseline.xml b/posthog-android/lint-baseline.xml index a9456408..08e91d61 100644 --- a/posthog-android/lint-baseline.xml +++ b/posthog-android/lint-baseline.xml @@ -36,13 +36,13 @@ + message="A newer version of androidx.compose.ui:ui than 1.0.0 is available: 1.7.4" + errorLine1=" implementation("androidx.compose.ui:ui:${PosthogBuildConfig.Dependencies.ANDROIDX_COMPOSE}")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="91" + column="20"/> diff --git a/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt b/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt index f87d507f..c9800205 100644 --- a/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt +++ b/posthog-android/src/main/java/com/posthog/android/replay/PostHogMaskModifier.kt @@ -3,17 +3,21 @@ package com.posthog.android.replay import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.SemanticsPropertyKey import androidx.compose.ui.semantics.semantics +import com.posthog.android.replay.PostHogReplayIntegration.Companion.PH_NO_CAPTURE_LABEL public object PostHogMaskModifier { - internal val PostHogReplayMask = SemanticsPropertyKey("ph-no-capture") + internal val PostHogReplayMask = SemanticsPropertyKey(PH_NO_CAPTURE_LABEL) /** - * Marks the element as not to be captured by PostHog Session Replay. + * Modifier to mask or unmask elements in the session replay. + * @param isEnabled If true, the element will be masked in the session replay. + * If false, the element will be unmasked in the session replay. + * This will override the defaults like maskAllTextInputs, maskAllImages etc. when used with the respective elements. */ - public fun Modifier.postHogMaskReplay(): Modifier { + public fun Modifier.postHogMask(isEnabled: Boolean = true): Modifier { return semantics( properties = { - this[PostHogReplayMask] = true + this[PostHogReplayMask] = isEnabled }, ) } diff --git a/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt b/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt index 6661c84d..447ec2f4 100644 --- a/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt @@ -49,7 +49,6 @@ import android.widget.TextView import androidx.compose.ui.node.RootForTest import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.semantics.getAllSemanticsNodes -import androidx.compose.ui.semantics.getOrNull import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import com.posthog.PostHog @@ -513,71 +512,67 @@ public class PostHogReplayIntegration( view: View, maskableWidgets: MutableList, ) { - if (view is TextView) { - val viewText = view.text?.toString() - var maskIt = false - if (!viewText.isNullOrEmpty()) { - maskIt = - view.shouldMaskTextView() - } - - val hint = view.hint?.toString() - if (!maskIt && !hint.isNullOrEmpty()) { - maskIt = - view.shouldMaskTextView() + when { + view.isComposeView() -> { + findMaskableComposeRects(view, maskableWidgets) } - if (maskIt) { + view.isNoCapture() -> { val rect = view.globalVisibleRect() maskableWidgets.add(rect) - return } - } - if (view is Spinner) { - if (view.shouldMaskSpinner()) { - val rect = view.globalVisibleRect() - maskableWidgets.add(rect) - return + view is TextView -> { + val viewText = view.text?.toString() + var maskIt = false + if (!viewText.isNullOrEmpty()) { + maskIt = + view.shouldMaskTextView() + } + + val hint = view.hint?.toString() + if (!maskIt && !hint.isNullOrEmpty()) { + maskIt = + view.shouldMaskTextView() + } + + if (maskIt) { + val rect = view.globalVisibleRect() + maskableWidgets.add(rect) + } } - } - if (view is ImageView) { - if (view.shouldMaskImage()) { - val rect = view.globalVisibleRect() - maskableWidgets.add(rect) - return + view is Spinner -> { + if (view.shouldMaskSpinner()) { + val rect = view.globalVisibleRect() + maskableWidgets.add(rect) + } } - } - if (view is WebView) { - if (view.isAnyInputSensitive()) { - val rect = view.globalVisibleRect() - maskableWidgets.add(rect) - return + view is ImageView -> { + if (view.shouldMaskImage()) { + val rect = view.globalVisibleRect() + maskableWidgets.add(rect) + } } - } - if (view.isComposeView()) { - findMaskableComposeRects(view, maskableWidgets) - } + view is WebView -> { + if (view.isAnyInputSensitive()) { + val rect = view.globalVisibleRect() + maskableWidgets.add(rect) + } + } - // if a view parent of any type is tagged as non masking, mask it - if (view.isNoCapture()) { - val rect = view.globalVisibleRect() - maskableWidgets.add(rect) - return - } + view is ViewGroup && view.childCount > 0 -> { + for (i in 0 until view.childCount) { + val viewChild = view.getChildAt(i) ?: continue - 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 (!viewChild.isVisible()) { - continue + findMaskableWidgets(viewChild, maskableWidgets) } - - findMaskableWidgets(viewChild, maskableWidgets) } } } @@ -594,21 +589,30 @@ public class PostHogReplayIntegration( val semanticsNodes = semanticsOwner.getAllSemanticsNodes(true) semanticsNodes.forEach { node -> - val isTextInput = node.config.contains(SemanticsProperties.EditableText) - val isPassword = node.config.contains(SemanticsProperties.Password) - val isImage = node.config.contains(SemanticsProperties.ContentDescription) - val shouldMaskAnyway = node.config.getOrNull(PostHogReplayMask) == true + val hasText = node.config.contains(SemanticsProperties.Text) + val hasEditableText = node.config.contains(SemanticsProperties.EditableText) + val hasPassword = node.config.contains(SemanticsProperties.Password) + val hasImage = node.config.contains(SemanticsProperties.ContentDescription) + + val hasMaskModifier = node.config.contains(PostHogReplayMask) + val isNoCapture = hasMaskModifier && node.config[PostHogReplayMask] - if (!shouldMaskAnyway) { - if (isTextInput && (config.sessionReplayConfig.maskAllTextInputs || isPassword)) { + when { + isNoCapture -> { rectsToMask.add(node.boundsInWindow.toRect()) } - if (isImage && config.sessionReplayConfig.maskAllImages) { - rectsToMask.add(node.boundsInWindow.toRect()) + !hasMaskModifier -> { + when { + (hasText || hasEditableText) && (config.sessionReplayConfig.maskAllTextInputs || hasPassword) -> { + rectsToMask.add(node.boundsInWindow.toRect()) + } + + hasImage && config.sessionReplayConfig.maskAllImages -> { + rectsToMask.add(node.boundsInWindow.toRect()) + } + } } - } else { - rectsToMask.add(node.boundsInWindow.toRect()) } } } @@ -1231,9 +1235,9 @@ public class PostHogReplayIntegration( return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O } - private companion object { - private const val PH_NO_CAPTURE_LABEL = "ph-no-capture" - private const val ANDROID_COMPOSE_VIEW_CLASS_NAME = "androidx.compose.ui.platform.AndroidComposeView" - private const val ANDROID_COMPOSE_VIEW = "AndroidComposeView" + internal companion object { + const val PH_NO_CAPTURE_LABEL: String = "ph-no-capture" + const val ANDROID_COMPOSE_VIEW_CLASS_NAME: String = "androidx.compose.ui.platform.AndroidComposeView" + const val ANDROID_COMPOSE_VIEW: String = "AndroidComposeView" } } From 9d889473dba5c3d4fa0f2181cfe3b140d5131ab6 Mon Sep 17 00:00:00 2001 From: Subhradeep Date: Mon, 28 Oct 2024 21:25:01 +0530 Subject: [PATCH 4/8] compose-ui dependency from impl to compileOnly --- posthog-android/build.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/posthog-android/build.gradle.kts b/posthog-android/build.gradle.kts index dc081a1b..5b5d4bfd 100644 --- a/posthog-android/build.gradle.kts +++ b/posthog-android/build.gradle.kts @@ -88,9 +88,11 @@ dependencies { implementation("androidx.lifecycle:lifecycle-process:${PosthogBuildConfig.Dependencies.LIFECYCLE}") implementation("androidx.lifecycle:lifecycle-common-java8:${PosthogBuildConfig.Dependencies.LIFECYCLE}") implementation("androidx.core:core:${PosthogBuildConfig.Dependencies.ANDROIDX_CORE}") - implementation("androidx.compose.ui:ui:${PosthogBuildConfig.Dependencies.ANDROIDX_COMPOSE}") implementation("com.squareup.curtains:curtains:${PosthogBuildConfig.Dependencies.CURTAINS}") + // compile only + compileOnly("androidx.compose.ui:ui:${PosthogBuildConfig.Dependencies.ANDROIDX_COMPOSE}") + // compatibility signature("org.codehaus.mojo.signature:java18:${PosthogBuildConfig.Plugins.SIGNATURE_JAVA18}@signature") signature( From e16c3bc43d8c7f6410fc802d6854f63e3c6ad83d Mon Sep 17 00:00:00 2001 From: Subhradeep Date: Mon, 28 Oct 2024 21:53:18 +0530 Subject: [PATCH 5/8] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 995fe2f7..91fa3a28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Next +- recording: add replay masking to jetpack compose views ([#198](https://github.com/PostHog/posthog-android/pull/198)) + ## 3.8.3 - 2024-10-25 - recording: fix crash when calling view.isVisible ([#201](https://github.com/PostHog/posthog-android/pull/201)) From 7d06b3dbe3dbfb8ae2ad423dd0e0d091c3bdbc79 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 30 Oct 2024 08:41:40 +0100 Subject: [PATCH 6/8] fix --- posthog-android/lint-baseline.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/posthog-android/lint-baseline.xml b/posthog-android/lint-baseline.xml index 08e91d61..d669a478 100644 --- a/posthog-android/lint-baseline.xml +++ b/posthog-android/lint-baseline.xml @@ -37,12 +37,12 @@ + errorLine1=" compileOnly("androidx.compose.ui:ui:${PosthogBuildConfig.Dependencies.ANDROIDX_COMPOSE}")" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="94" + column="17"/> From dcaf6f4cfdcfe9c71d5c7f69e4222bb0cc03636e Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 30 Oct 2024 09:08:58 +0100 Subject: [PATCH 7/8] rename --- .../android/replay/PostHogReplayIntegration.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt b/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt index 8437a6c7..9a936a92 100644 --- a/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt @@ -540,7 +540,7 @@ public class PostHogReplayIntegration( ) { when { view.isComposeView() -> { - findMaskableComposeRects(view, maskableWidgets) + findMaskableComposeWidgets(view, maskableWidgets) } view.isNoCapture() -> { @@ -603,9 +603,9 @@ public class PostHogReplayIntegration( } } - private fun findMaskableComposeRects( + private fun findMaskableComposeWidgets( view: View, - rectsToMask: MutableList, + maskableWidgets: MutableList, ) { val semanticsOwner = (view as? RootForTest)?.semanticsOwner ?: run { @@ -625,17 +625,17 @@ public class PostHogReplayIntegration( when { isNoCapture -> { - rectsToMask.add(node.boundsInWindow.toRect()) + maskableWidgets.add(node.boundsInWindow.toRect()) } !hasMaskModifier -> { when { (hasText || hasEditableText) && (config.sessionReplayConfig.maskAllTextInputs || hasPassword) -> { - rectsToMask.add(node.boundsInWindow.toRect()) + maskableWidgets.add(node.boundsInWindow.toRect()) } hasImage && config.sessionReplayConfig.maskAllImages -> { - rectsToMask.add(node.boundsInWindow.toRect()) + maskableWidgets.add(node.boundsInWindow.toRect()) } } } From 30843a373259b993798c7525297d7f9cacc9068a Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 30 Oct 2024 09:18:40 +0100 Subject: [PATCH 8/8] swallow errors --- posthog-android/consumer-rules.pro | 4 ++ .../replay/PostHogReplayIntegration.kt | 53 ++++++++++--------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/posthog-android/consumer-rules.pro b/posthog-android/consumer-rules.pro index 1553107e..25de34ea 100644 --- a/posthog-android/consumer-rules.pro +++ b/posthog-android/consumer-rules.pro @@ -77,4 +77,8 @@ -dontwarn org.conscrypt.** -dontwarn org.bouncycastle.** -dontwarn org.openjsse.** + +# used in reflection to check if compose is available at runtime +-keepnames class androidx.compose.ui.platform.AndroidComposeView + ##---------------End: proguard configuration for okhttp3 ---------- \ No newline at end of file diff --git a/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt b/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt index 9a936a92..2d803c85 100644 --- a/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt +++ b/posthog-android/src/main/java/com/posthog/android/replay/PostHogReplayIntegration.kt @@ -607,39 +607,44 @@ public class PostHogReplayIntegration( view: View, maskableWidgets: MutableList, ) { - val semanticsOwner = - (view as? RootForTest)?.semanticsOwner ?: run { - config.logger.log("View is not a RootForTest: $view") - return - } - val semanticsNodes = semanticsOwner.getAllSemanticsNodes(true) + try { + val semanticsOwner = + (view as? RootForTest)?.semanticsOwner ?: run { + config.logger.log("View is not a RootForTest: $view") + return + } + val semanticsNodes = semanticsOwner.getAllSemanticsNodes(true) - semanticsNodes.forEach { node -> - val hasText = node.config.contains(SemanticsProperties.Text) - val hasEditableText = node.config.contains(SemanticsProperties.EditableText) - val hasPassword = node.config.contains(SemanticsProperties.Password) - val hasImage = node.config.contains(SemanticsProperties.ContentDescription) + semanticsNodes.forEach { node -> + val hasText = node.config.contains(SemanticsProperties.Text) + val hasEditableText = node.config.contains(SemanticsProperties.EditableText) + val hasPassword = node.config.contains(SemanticsProperties.Password) + val hasImage = node.config.contains(SemanticsProperties.ContentDescription) - val hasMaskModifier = node.config.contains(PostHogReplayMask) - val isNoCapture = hasMaskModifier && node.config[PostHogReplayMask] + val hasMaskModifier = node.config.contains(PostHogReplayMask) + val isNoCapture = hasMaskModifier && node.config[PostHogReplayMask] - when { - isNoCapture -> { - maskableWidgets.add(node.boundsInWindow.toRect()) - } + when { + isNoCapture -> { + maskableWidgets.add(node.boundsInWindow.toRect()) + } - !hasMaskModifier -> { - when { - (hasText || hasEditableText) && (config.sessionReplayConfig.maskAllTextInputs || hasPassword) -> { - maskableWidgets.add(node.boundsInWindow.toRect()) - } + !hasMaskModifier -> { + when { + (hasText || hasEditableText) && (config.sessionReplayConfig.maskAllTextInputs || hasPassword) -> { + maskableWidgets.add(node.boundsInWindow.toRect()) + } - hasImage && config.sessionReplayConfig.maskAllImages -> { - maskableWidgets.add(node.boundsInWindow.toRect()) + hasImage && config.sessionReplayConfig.maskAllImages -> { + maskableWidgets.add(node.boundsInWindow.toRect()) + } } } } } + } catch (e: Throwable) { + // swallow possible errors due to compose versioning, etc + config.logger.log("Session Replay findMaskableComposeWidgets failed: $e") } }