diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt index 652d1d36da..a412efe2cb 100644 --- a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt @@ -81,22 +81,39 @@ internal object ComposeReflection { val PainterFieldOfContentPainterModifier = ContentPainterModifierClass?.getDeclaredFieldSafe("painter") - val ContentPainterElementClass = getClassSafe("coil.compose.ContentPainterElement") + val ContentPainterElementClass = getClassSafe( + "coil.compose.ContentPainterElement", + isCritical = false + ) val PainterFieldOfContentPainterElement = ContentPainterElementClass?.getDeclaredFieldSafe("painter") - val AsyncImagePainterClass = getClassSafe("coil.compose.AsyncImagePainter") + val AsyncImagePainterClass = getClassSafe( + "coil.compose.AsyncImagePainter", + isCritical = false + ) val PainterFieldOfAsyncImagePainter = AsyncImagePainterClass?.getDeclaredFieldSafe("_painter") // Region of MultiParagraph text - val ParagraphInfoListField = - MultiParagraph::class.java.getDeclaredFieldSafe("paragraphInfoList") + val ParagraphInfoListField = MultiParagraph::class.java.getDeclaredFieldSafe( + "paragraphInfoList", + isCritical = false + ) val ParagraphInfoClass = getClassSafe("androidx.compose.ui.text.ParagraphInfo") - val ParagraphField = ParagraphInfoClass?.getDeclaredFieldSafe("paragraph") + val ParagraphField = ParagraphInfoClass?.getDeclaredFieldSafe( + "paragraph", + isCritical = false + ) val AndroidParagraphClass = getClassSafe("androidx.compose.ui.text.AndroidParagraph") - val LayoutField = AndroidParagraphClass?.getDeclaredFieldSafe("layout") + val LayoutField = AndroidParagraphClass?.getDeclaredFieldSafe( + "layout", + isCritical = false + ) val TextLayoutClass = getClassSafe("androidx.compose.ui.text.android.TextLayout") - val StaticLayoutField = TextLayoutClass?.getDeclaredFieldSafe("layout") + val StaticLayoutField = TextLayoutClass?.getDeclaredFieldSafe( + "layout", + isCritical = false + ) } internal fun Field.accessible(): Field { @@ -128,49 +145,70 @@ internal fun Field.getSafe(target: Any?): Any? { } } -internal fun getClassSafe(className: String): Class<*>? { +internal fun getClassSafe(className: String, isCritical: Boolean = true): Class<*>? { return try { Class.forName(className) } catch (e: LinkageError) { - logReflectionException(className, LOG_TYPE_CLASS, LOG_REASON_LINKAGE_ERROR, e) + if (isCritical) { + logReflectionException(className, LOG_TYPE_CLASS, LOG_REASON_LINKAGE_ERROR, e) + } null } catch (e: ExceptionInInitializerError) { - logReflectionException(className, LOG_TYPE_CLASS, LOG_REASON_INITIALIZATION_ERROR, e) + if (isCritical) { + logReflectionException(className, LOG_TYPE_CLASS, LOG_REASON_INITIALIZATION_ERROR, e) + } null } catch (e: ClassNotFoundException) { - logNoSuchException(className, LOG_TYPE_CLASS, e) + if (isCritical) { + logNoSuchException(className, LOG_TYPE_CLASS, e) + } null } } @Suppress("TooGenericExceptionCaught") -internal fun Class<*>.getDeclaredFieldSafe(fieldName: String): Field? { +internal fun Class<*>.getDeclaredFieldSafe(fieldName: String, isCritical: Boolean = true): Field? { return try { getDeclaredField(fieldName).accessible() } catch (e: SecurityException) { - logSecurityException(fieldName, LOG_TYPE_FIELD, e) + if (isCritical) { + logSecurityException(fieldName, LOG_TYPE_FIELD, e) + } null } catch (e: NullPointerException) { - logNullPointerException(fieldName, LOG_TYPE_FIELD, e) + if (isCritical) { + logNullPointerException(fieldName, LOG_TYPE_FIELD, e) + } null } catch (e: NoSuchFieldException) { - logNoSuchException(fieldName, LOG_TYPE_FIELD, e) + if (isCritical) { + logNoSuchException(fieldName, LOG_TYPE_FIELD, e) + } null } } @Suppress("TooGenericExceptionCaught") -internal fun Class<*>.getDeclaredMethodSafe(methodName: String): Method? { +internal fun Class<*>.getDeclaredMethodSafe( + methodName: String, + isCritical: Boolean = true +): Method? { return try { getDeclaredMethod(methodName).accessible() } catch (e: SecurityException) { - logSecurityException(methodName, LOG_TYPE_METHOD, e) + if (isCritical) { + logSecurityException(methodName, LOG_TYPE_METHOD, e) + } null } catch (e: NullPointerException) { - logNullPointerException(methodName, LOG_TYPE_METHOD, e) + if (isCritical) { + logNullPointerException(methodName, LOG_TYPE_METHOD, e) + } null } catch (e: NoSuchMethodException) { - logNoSuchException(methodName, LOG_TYPE_METHOD, e) + if (isCritical) { + logNoSuchException(methodName, LOG_TYPE_METHOD, e) + } null } } diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt index 0d9bd2e4ad..00edf24cdd 100644 --- a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.VectorPainter import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.SemanticsActions @@ -24,6 +25,9 @@ import androidx.compose.ui.semantics.getOrNull import androidx.compose.ui.text.TextLayoutInput import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.unit.Density +import com.datadog.android.Datadog +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.TouchPrivacy @@ -250,6 +254,7 @@ internal class SemanticsUtils(private val reflectionUtils: ReflectionUtils = Ref is BitmapPainter -> reflectionUtils.getBitmapInBitmapPainter(painter) is VectorPainter -> reflectionUtils.getBitmapInVectorPainter(painter) else -> { + logUnsupportedPainter(painter) null } } @@ -263,6 +268,22 @@ internal class SemanticsUtils(private val reflectionUtils: ReflectionUtils = Ref } } + private fun logUnsupportedPainter(painter: Painter?) { + val painterType = painter?.javaClass?.simpleName ?: "null" + (Datadog.getInstance() as? FeatureSdkCore)?.internalLogger?.log( + level = InternalLogger.Level.ERROR, + targets = listOf( + InternalLogger.Target.MAINTAINER, + InternalLogger.Target.TELEMETRY + ), + messageBuilder = { "Unsupported painter type in Compose: $painterType" }, + onlyOnce = true, + additionalProperties = mapOf( + "painter.type" to painterType + ) + ) + } + private fun resolveModifierColor(semanticsNode: SemanticsNode): Color? { val modifier = semanticsNode.layoutInfo.getModifierInfo().firstOrNull { reflectionUtils.isTextStringSimpleElement(it.modifier)