Skip to content

Commit

Permalink
RUM-6195: Add FGM and proguard rules
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanmos committed Nov 27, 2024
1 parent 1fb0caf commit f23bb5f
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 54 deletions.
4 changes: 2 additions & 2 deletions detekt_custom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,11 @@ datadog:
- "android.database.sqlite.SQLiteDatabase.setTransactionSuccessful():java.lang.IllegalStateException"
- "android.graphics.Bitmap.compress(android.graphics.Bitmap.CompressFormat, kotlin.Int, java.io.OutputStream):java.lang.NullPointerException,java.lang.IllegalArgumentException"
- "android.graphics.Bitmap.copy(android.graphics.Bitmap.Config, kotlin.Boolean):java.lang.IllegalArgumentException"
- "android.graphics.Bitmap.createBitmap(kotlin.Int, kotlin.Int, android.graphics.Bitmap.Config):java.lang.IllegalArgumentException"
- "android.graphics.Bitmap.createBitmap(android.util.DisplayMetrics?, kotlin.Int, kotlin.Int, android.graphics.Bitmap.Config):java.lang.IllegalArgumentException"
- "android.graphics.Bitmap.createBitmap(kotlin.Int, kotlin.Int, android.graphics.Bitmap.Config):java.lang.IllegalArgumentException"
- "android.graphics.Bitmap.createScaledBitmap(android.graphics.Bitmap, kotlin.Int, kotlin.Int, kotlin.Boolean):java.lang.IllegalArgumentException"
- "android.graphics.Color.parseColor(kotlin.String?):java.lang.IllegalArgumentException"
- "android.graphics.Canvas.constructor(android.graphics.Bitmap):java.lang.IllegalStateException"
- "android.graphics.Color.parseColor(kotlin.String?):java.lang.IllegalArgumentException"
- "android.graphics.drawable.LayerDrawable.getDrawable(kotlin.Int):java.lang.IndexOutOfBoundsException"
- "android.net.ConnectivityManager.registerDefaultNetworkCallback(android.net.ConnectivityManager.NetworkCallback):java.lang.IllegalArgumentException,java.lang.SecurityException"
- "android.net.ConnectivityManager.unregisterNetworkCallback(android.net.ConnectivityManager.NetworkCallback):java.lang.SecurityException"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
-keepclassmembers class androidx.compose.foundation.text.modifiers.TextStringSimpleElement {
<fields>;
}
-keepclassmembers class androidx.compose.material.CheckDrawingCache {
<fields>;
}
-keepclassmembers class androidx.compose.material.CheckboxKt {
<fields>;
}
-keepclassmembers class androidx.compose.ui.draw.DrawBehindElement {
<fields>;
}
-keepclassmembers class androidx.compose.foundation.BackgroundElement {
<fields>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.state.ToggleableState
import com.datadog.android.sessionreplay.ImagePrivacy
import com.datadog.android.sessionreplay.TextAndInputPrivacy
import com.datadog.android.sessionreplay.compose.internal.data.SemanticsWireframe
import com.datadog.android.sessionreplay.compose.internal.data.UiContext
import com.datadog.android.sessionreplay.compose.internal.utils.PathUtils
Expand All @@ -33,27 +34,70 @@ internal class CheckboxSemanticsNodeMapper(
): SemanticsWireframe {
val globalBounds = resolveBounds(semanticsNode)

val wireframes = if (isCheckboxChecked(semanticsNode)) {
createCheckedWireframes(
parentContext = parentContext,
asyncJobStatusCallback = asyncJobStatusCallback,
val checkableWireframes = if (parentContext.textAndInputPrivacy != TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) {
resolveMaskedCheckable(
semanticsNode = semanticsNode,
globalBounds = globalBounds
)
} else {
createUncheckedWireframes(
// Resolves checkable view regardless the state
resolveCheckable(
semanticsNode = semanticsNode,
globalBounds = globalBounds,
backgroundColor = DEFAULT_COLOR_WHITE
parentContext = parentContext,
asyncJobStatusCallback = asyncJobStatusCallback,
globalBounds = globalBounds
)
}

return SemanticsWireframe(
uiContext = null,
wireframes = wireframes
wireframes = checkableWireframes
)
}

private fun resolveMaskedCheckable(
semanticsNode: SemanticsNode,
globalBounds: GlobalBounds
): List<MobileSegment.Wireframe> {
// TODO RUM-5118: Decide how to display masked checkbox, Currently use old unchecked shape wireframe,
return createUncheckedWireframes(
semanticsNode = semanticsNode,
globalBounds = globalBounds,
backgroundColor = DEFAULT_COLOR_WHITE,
borderColor = DEFAULT_COLOR_BLACK,
currentIndex = 0
)
}

private fun resolveCheckable(
semanticsNode: SemanticsNode,
parentContext: UiContext,
asyncJobStatusCallback: AsyncJobStatusCallback,
globalBounds: GlobalBounds
): List<MobileSegment.Wireframe> =
if (isCheckboxChecked(semanticsNode)) {
createCheckedWireframes(
parentContext = parentContext,
asyncJobStatusCallback = asyncJobStatusCallback,
semanticsNode = semanticsNode,
globalBounds = globalBounds
)
} else {
val borderColor =
semanticsUtils.resolveBorderColor(semanticsNode)
?.let { rawColor ->
convertColor(rawColor)
} ?: DEFAULT_COLOR_BLACK

createUncheckedWireframes(
semanticsNode = semanticsNode,
globalBounds = globalBounds,
backgroundColor = DEFAULT_COLOR_WHITE,
borderColor = borderColor,
currentIndex = 0
)
}

private fun createCheckedWireframes(
parentContext: UiContext,
asyncJobStatusCallback: AsyncJobStatusCallback,
Expand Down Expand Up @@ -119,18 +163,32 @@ internal class CheckboxSemanticsNodeMapper(
): List<MobileSegment.Wireframe> {
val strokeColor = getFallbackCheckmarkColor(backgroundColor)

val background: MobileSegment.Wireframe = createUncheckedWireframes(
val wireframesList = mutableListOf<MobileSegment.Wireframe>()
var index = 0

val borderColor =
semanticsUtils.resolveBorderColor(semanticsNode)
?.let { rawColor ->
convertColor(rawColor)
} ?: DEFAULT_COLOR_BLACK

createUncheckedWireframes(
semanticsNode = semanticsNode,
globalBounds = globalBounds,
backgroundColor = backgroundColor
)[0]
backgroundColor = backgroundColor,
borderColor = borderColor,
currentIndex = 0
).firstOrNull()?.let {
wireframesList.add(it)
index++
}

val checkmarkWidth = globalBounds.width * CHECKMARK_SIZE_FACTOR
val checkmarkHeight = globalBounds.height * CHECKMARK_SIZE_FACTOR
val xPos = globalBounds.x + ((globalBounds.width / 2) - (checkmarkWidth / 2))
val yPos = globalBounds.y + ((globalBounds.height / 2) - (checkmarkHeight / 2))
val foreground: MobileSegment.Wireframe = MobileSegment.Wireframe.ShapeWireframe(
id = resolveId(semanticsNode, 1),
val foreground = MobileSegment.Wireframe.ShapeWireframe(
id = resolveId(semanticsNode, index),
x = xPos.toLong(),
y = yPos.toLong(),
width = checkmarkWidth.toLong(),
Expand All @@ -145,23 +203,21 @@ internal class CheckboxSemanticsNodeMapper(
width = BOX_BORDER_WIDTH_DP
)
)
return listOf(background, foreground)

wireframesList.add(foreground)
return wireframesList
}

private fun createUncheckedWireframes(
semanticsNode: SemanticsNode,
globalBounds: GlobalBounds,
backgroundColor: String
backgroundColor: String,
borderColor: String,
currentIndex: Int
): List<MobileSegment.Wireframe> {
val borderColor =
semanticsUtils.resolveBorderColor(semanticsNode)
?.let { rawColor ->
convertColor(rawColor)
} ?: DEFAULT_COLOR_BLACK

return listOf(
MobileSegment.Wireframe.ShapeWireframe(
id = resolveId(semanticsNode, 0),
id = resolveId(semanticsNode, currentIndex),
x = globalBounds.x,
y = globalBounds.y,
width = globalBounds.width,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.Chec
import com.datadog.android.sessionreplay.recorder.wrappers.BitmapWrapper
import com.datadog.android.sessionreplay.recorder.wrappers.CanvasWrapper
import java.util.Locale
import android.graphics.Path as AndroidPath

internal class PathUtils(
private val logger: InternalLogger = InternalLogger.UNBOUND,
private val canvasWrapper: CanvasWrapper = CanvasWrapper(logger),
private val bitmapWrapper: BitmapWrapper = BitmapWrapper()
) {
@Suppress("UnsafeThirdPartyFunctionCall") // handling IllegalArgumentException
internal fun parseColorSafe(color: String): Int? {
return try {
@Suppress("UnsafeThirdPartyFunctionCall") // handling IllegalArgumentException
Color.parseColor(color)
} catch (e: IllegalArgumentException) {
logger.log(
Expand Down Expand Up @@ -56,9 +57,9 @@ internal class PathUtils(
return "#$alphaValue$rgbColor"
}

@Suppress("UnsafeThirdPartyFunctionCall") // handling UnsupportedOperationException
internal fun asAndroidPathSafe(path: Path): android.graphics.Path? {
internal fun asAndroidPathSafe(path: Path): AndroidPath? {
return try {
@Suppress("UnsafeThirdPartyFunctionCall") // handling UnsupportedOperationException
path.asAndroidPath()
} catch (e: UnsupportedOperationException) {
logger.log(
Expand Down Expand Up @@ -144,28 +145,18 @@ internal class PathUtils(
checkmarkColor: Int
): Bitmap? {
val canvas = canvasWrapper.createCanvas(bitmap) ?: return null
drawCanvasBackground(canvas, fillColor)
drawCanvasForeground(canvas, scaledPath, checkmarkColor)
return bitmap
}

private fun drawCanvasBackground(
canvas: Canvas,
fillColor: Int
) {
// draw the background
canvas.drawColor(fillColor)
}

private fun drawCanvasForeground(
canvas: Canvas,
path: Path,
checkmarkColor: Int
) {
drawPathToBitmap(checkmarkColor, path, canvas)
// draw the checkmark
drawPathToBitmap(checkmarkColor, scaledPath, canvas)

return bitmap
}

@Suppress("UnsafeThirdPartyFunctionCall") // handling IllegalArgumentException
private fun drawPathSafe(canvas: Canvas?, path: android.graphics.Path, paint: Paint) {
private fun drawPathSafe(canvas: Canvas?, path: AndroidPath, paint: Paint) {
try {
canvas?.drawPath(path, paint)
} catch (e: IllegalArgumentException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
package com.datadog.android.sessionreplay.compose.internal.utils

import android.view.View
import androidx.compose.animation.core.AnimationState
import androidx.compose.runtime.Composition
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorProducer
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.semantics.SemanticsNode
Expand Down Expand Up @@ -48,6 +50,10 @@ internal class ReflectionUtils {
return ComposeReflection.AndroidComposeViewClass?.isInstance(any) == true
}

fun isDrawBehindElementClass(modifier: Modifier): Boolean {
return ComposeReflection.DrawBehindElementClass?.isInstance(modifier) == true
}

fun getOwner(composition: Composition): Any? {
return ComposeReflection.OwnerField?.getSafe(composition)
}
Expand Down Expand Up @@ -97,4 +103,28 @@ internal class ReflectionUtils {
fun getClipShape(modifier: Modifier): Shape? {
return ComposeReflection.ClipShapeField?.getSafe(modifier) as? Shape
}

fun getOnDraw(modifier: Modifier): Any? {
return ComposeReflection.OnDrawField?.getSafe(modifier)
}

fun getBoxColor(onDrawInstance: Any): AnimationState<*, *>? {
return ComposeReflection.BoxColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
}

fun getCheckColor(onDrawInstance: Any): AnimationState<*, *>? {
return ComposeReflection.CheckColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
}

fun getBorderColor(onDrawInstance: Any): AnimationState<*, *>? {
return ComposeReflection.BorderColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
}

fun getCheckCache(onDrawInstance: Any): Any? {
return ComposeReflection.CheckCacheField?.getSafe(onDrawInstance)
}

fun getCheckPath(checkCache: Any): Path? {
return ComposeReflection.CheckPathField?.getSafe(checkCache) as? Path
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package com.datadog.android.sessionreplay.compose.internal.utils

import android.view.View
import androidx.compose.animation.core.AnimationState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
Expand All @@ -21,7 +20,6 @@ import androidx.compose.ui.text.TextLayoutInput
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.unit.Density
import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.TextLayoutInfo
import com.datadog.android.sessionreplay.compose.internal.reflection.ComposeReflection
import com.datadog.android.sessionreplay.utils.GlobalBounds

@Suppress("TooManyFunctions")
Expand Down Expand Up @@ -108,8 +106,8 @@ internal class SemanticsUtils(private val reflectionUtils: ReflectionUtils = Ref

internal fun resolveCheckPath(semanticsNode: SemanticsNode): Path? =
resolveOnDrawInstance(semanticsNode)?.let { onDraw ->
ComposeReflection.CheckCacheField?.getSafe(onDraw)?.let { checkCache ->
ComposeReflection.CheckPathField?.getSafe(checkCache) as? Path
reflectionUtils.getCheckCache(onDraw)?.let { checkCache ->
reflectionUtils.getCheckPath(checkCache)
}
}

Expand Down Expand Up @@ -241,32 +239,33 @@ internal class SemanticsUtils(private val reflectionUtils: ReflectionUtils = Ref
private fun resolveOnDrawInstance(semanticsNode: SemanticsNode): Any? {
val drawBehindElement =
semanticsNode.layoutInfo.getModifierInfo().firstOrNull { modifierInfo ->
ComposeReflection.DrawBehindElementClass?.isInstance(modifierInfo.modifier) == true
reflectionUtils.isDrawBehindElementClass(modifierInfo.modifier)
}?.modifier

return drawBehindElement?.let {
ComposeReflection.OnDrawField?.getSafe(drawBehindElement)
reflectionUtils.getOnDraw(it)
}
}

private fun resolveReflectedProperty(semanticsNode: SemanticsNode, fieldType: CheckmarkFieldType): Long? {
val onDrawInstance = resolveOnDrawInstance(semanticsNode)

val checkmarkColor: AnimationState<*, *>? = onDrawInstance?.let {
val color = onDrawInstance?.let {
when (fieldType) {
CheckmarkFieldType.FILL_COLOR -> {
ComposeReflection.BoxColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
reflectionUtils.getBoxColor(onDrawInstance)
}
CheckmarkFieldType.CHECKMARK_COLOR -> {
ComposeReflection.CheckColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
reflectionUtils.getCheckColor(onDrawInstance)
}
CheckmarkFieldType.BORDER_COLOR -> {
ComposeReflection.BorderColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
reflectionUtils.getBorderColor(onDrawInstance)
}
}
}

val result = (checkmarkColor?.value as? androidx.compose.ui.graphics.Color)?.value
val result = (color?.value as? Color)
?.value

return result?.toLong()
}
Expand Down
Loading

0 comments on commit f23bb5f

Please sign in to comment.