Skip to content

Commit f23bb5f

Browse files
committed
RUM-6195: Add FGM and proguard rules
1 parent 1fb0caf commit f23bb5f

File tree

10 files changed

+258
-54
lines changed

10 files changed

+258
-54
lines changed

detekt_custom.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ datadog:
129129
- "android.database.sqlite.SQLiteDatabase.setTransactionSuccessful():java.lang.IllegalStateException"
130130
- "android.graphics.Bitmap.compress(android.graphics.Bitmap.CompressFormat, kotlin.Int, java.io.OutputStream):java.lang.NullPointerException,java.lang.IllegalArgumentException"
131131
- "android.graphics.Bitmap.copy(android.graphics.Bitmap.Config, kotlin.Boolean):java.lang.IllegalArgumentException"
132-
- "android.graphics.Bitmap.createBitmap(kotlin.Int, kotlin.Int, android.graphics.Bitmap.Config):java.lang.IllegalArgumentException"
133132
- "android.graphics.Bitmap.createBitmap(android.util.DisplayMetrics?, kotlin.Int, kotlin.Int, android.graphics.Bitmap.Config):java.lang.IllegalArgumentException"
133+
- "android.graphics.Bitmap.createBitmap(kotlin.Int, kotlin.Int, android.graphics.Bitmap.Config):java.lang.IllegalArgumentException"
134134
- "android.graphics.Bitmap.createScaledBitmap(android.graphics.Bitmap, kotlin.Int, kotlin.Int, kotlin.Boolean):java.lang.IllegalArgumentException"
135-
- "android.graphics.Color.parseColor(kotlin.String?):java.lang.IllegalArgumentException"
136135
- "android.graphics.Canvas.constructor(android.graphics.Bitmap):java.lang.IllegalStateException"
136+
- "android.graphics.Color.parseColor(kotlin.String?):java.lang.IllegalArgumentException"
137137
- "android.graphics.drawable.LayerDrawable.getDrawable(kotlin.Int):java.lang.IndexOutOfBoundsException"
138138
- "android.net.ConnectivityManager.registerDefaultNetworkCallback(android.net.ConnectivityManager.NetworkCallback):java.lang.IllegalArgumentException,java.lang.SecurityException"
139139
- "android.net.ConnectivityManager.unregisterNetworkCallback(android.net.ConnectivityManager.NetworkCallback):java.lang.SecurityException"

features/dd-sdk-android-session-replay-compose/consumer-rules.pro

+9
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@
1414
-keepclassmembers class androidx.compose.foundation.text.modifiers.TextStringSimpleElement {
1515
<fields>;
1616
}
17+
-keepclassmembers class androidx.compose.material.CheckDrawingCache {
18+
<fields>;
19+
}
20+
-keepclassmembers class androidx.compose.material.CheckboxKt {
21+
<fields>;
22+
}
23+
-keepclassmembers class androidx.compose.ui.draw.DrawBehindElement {
24+
<fields>;
25+
}
1726
-keepclassmembers class androidx.compose.foundation.BackgroundElement {
1827
<fields>;
1928
}

features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/CheckboxSemanticsNodeMapper.kt

+78-22
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.compose.ui.semantics.SemanticsProperties
1111
import androidx.compose.ui.semantics.getOrNull
1212
import androidx.compose.ui.state.ToggleableState
1313
import com.datadog.android.sessionreplay.ImagePrivacy
14+
import com.datadog.android.sessionreplay.TextAndInputPrivacy
1415
import com.datadog.android.sessionreplay.compose.internal.data.SemanticsWireframe
1516
import com.datadog.android.sessionreplay.compose.internal.data.UiContext
1617
import com.datadog.android.sessionreplay.compose.internal.utils.PathUtils
@@ -33,27 +34,70 @@ internal class CheckboxSemanticsNodeMapper(
3334
): SemanticsWireframe {
3435
val globalBounds = resolveBounds(semanticsNode)
3536

36-
val wireframes = if (isCheckboxChecked(semanticsNode)) {
37-
createCheckedWireframes(
38-
parentContext = parentContext,
39-
asyncJobStatusCallback = asyncJobStatusCallback,
37+
val checkableWireframes = if (parentContext.textAndInputPrivacy != TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) {
38+
resolveMaskedCheckable(
4039
semanticsNode = semanticsNode,
4140
globalBounds = globalBounds
4241
)
4342
} else {
44-
createUncheckedWireframes(
43+
// Resolves checkable view regardless the state
44+
resolveCheckable(
4545
semanticsNode = semanticsNode,
46-
globalBounds = globalBounds,
47-
backgroundColor = DEFAULT_COLOR_WHITE
46+
parentContext = parentContext,
47+
asyncJobStatusCallback = asyncJobStatusCallback,
48+
globalBounds = globalBounds
4849
)
4950
}
5051

5152
return SemanticsWireframe(
5253
uiContext = null,
53-
wireframes = wireframes
54+
wireframes = checkableWireframes
5455
)
5556
}
5657

58+
private fun resolveMaskedCheckable(
59+
semanticsNode: SemanticsNode,
60+
globalBounds: GlobalBounds
61+
): List<MobileSegment.Wireframe> {
62+
// TODO RUM-5118: Decide how to display masked checkbox, Currently use old unchecked shape wireframe,
63+
return createUncheckedWireframes(
64+
semanticsNode = semanticsNode,
65+
globalBounds = globalBounds,
66+
backgroundColor = DEFAULT_COLOR_WHITE,
67+
borderColor = DEFAULT_COLOR_BLACK,
68+
currentIndex = 0
69+
)
70+
}
71+
72+
private fun resolveCheckable(
73+
semanticsNode: SemanticsNode,
74+
parentContext: UiContext,
75+
asyncJobStatusCallback: AsyncJobStatusCallback,
76+
globalBounds: GlobalBounds
77+
): List<MobileSegment.Wireframe> =
78+
if (isCheckboxChecked(semanticsNode)) {
79+
createCheckedWireframes(
80+
parentContext = parentContext,
81+
asyncJobStatusCallback = asyncJobStatusCallback,
82+
semanticsNode = semanticsNode,
83+
globalBounds = globalBounds
84+
)
85+
} else {
86+
val borderColor =
87+
semanticsUtils.resolveBorderColor(semanticsNode)
88+
?.let { rawColor ->
89+
convertColor(rawColor)
90+
} ?: DEFAULT_COLOR_BLACK
91+
92+
createUncheckedWireframes(
93+
semanticsNode = semanticsNode,
94+
globalBounds = globalBounds,
95+
backgroundColor = DEFAULT_COLOR_WHITE,
96+
borderColor = borderColor,
97+
currentIndex = 0
98+
)
99+
}
100+
57101
private fun createCheckedWireframes(
58102
parentContext: UiContext,
59103
asyncJobStatusCallback: AsyncJobStatusCallback,
@@ -119,18 +163,32 @@ internal class CheckboxSemanticsNodeMapper(
119163
): List<MobileSegment.Wireframe> {
120164
val strokeColor = getFallbackCheckmarkColor(backgroundColor)
121165

122-
val background: MobileSegment.Wireframe = createUncheckedWireframes(
166+
val wireframesList = mutableListOf<MobileSegment.Wireframe>()
167+
var index = 0
168+
169+
val borderColor =
170+
semanticsUtils.resolveBorderColor(semanticsNode)
171+
?.let { rawColor ->
172+
convertColor(rawColor)
173+
} ?: DEFAULT_COLOR_BLACK
174+
175+
createUncheckedWireframes(
123176
semanticsNode = semanticsNode,
124177
globalBounds = globalBounds,
125-
backgroundColor = backgroundColor
126-
)[0]
178+
backgroundColor = backgroundColor,
179+
borderColor = borderColor,
180+
currentIndex = 0
181+
).firstOrNull()?.let {
182+
wireframesList.add(it)
183+
index++
184+
}
127185

128186
val checkmarkWidth = globalBounds.width * CHECKMARK_SIZE_FACTOR
129187
val checkmarkHeight = globalBounds.height * CHECKMARK_SIZE_FACTOR
130188
val xPos = globalBounds.x + ((globalBounds.width / 2) - (checkmarkWidth / 2))
131189
val yPos = globalBounds.y + ((globalBounds.height / 2) - (checkmarkHeight / 2))
132-
val foreground: MobileSegment.Wireframe = MobileSegment.Wireframe.ShapeWireframe(
133-
id = resolveId(semanticsNode, 1),
190+
val foreground = MobileSegment.Wireframe.ShapeWireframe(
191+
id = resolveId(semanticsNode, index),
134192
x = xPos.toLong(),
135193
y = yPos.toLong(),
136194
width = checkmarkWidth.toLong(),
@@ -145,23 +203,21 @@ internal class CheckboxSemanticsNodeMapper(
145203
width = BOX_BORDER_WIDTH_DP
146204
)
147205
)
148-
return listOf(background, foreground)
206+
207+
wireframesList.add(foreground)
208+
return wireframesList
149209
}
150210

151211
private fun createUncheckedWireframes(
152212
semanticsNode: SemanticsNode,
153213
globalBounds: GlobalBounds,
154-
backgroundColor: String
214+
backgroundColor: String,
215+
borderColor: String,
216+
currentIndex: Int
155217
): List<MobileSegment.Wireframe> {
156-
val borderColor =
157-
semanticsUtils.resolveBorderColor(semanticsNode)
158-
?.let { rawColor ->
159-
convertColor(rawColor)
160-
} ?: DEFAULT_COLOR_BLACK
161-
162218
return listOf(
163219
MobileSegment.Wireframe.ShapeWireframe(
164-
id = resolveId(semanticsNode, 0),
220+
id = resolveId(semanticsNode, currentIndex),
165221
x = globalBounds.x,
166222
y = globalBounds.y,
167223
width = globalBounds.width,

features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/PathUtils.kt

+10-19
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@ import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.Chec
1919
import com.datadog.android.sessionreplay.recorder.wrappers.BitmapWrapper
2020
import com.datadog.android.sessionreplay.recorder.wrappers.CanvasWrapper
2121
import java.util.Locale
22+
import android.graphics.Path as AndroidPath
2223

2324
internal class PathUtils(
2425
private val logger: InternalLogger = InternalLogger.UNBOUND,
2526
private val canvasWrapper: CanvasWrapper = CanvasWrapper(logger),
2627
private val bitmapWrapper: BitmapWrapper = BitmapWrapper()
2728
) {
28-
@Suppress("UnsafeThirdPartyFunctionCall") // handling IllegalArgumentException
2929
internal fun parseColorSafe(color: String): Int? {
3030
return try {
31+
@Suppress("UnsafeThirdPartyFunctionCall") // handling IllegalArgumentException
3132
Color.parseColor(color)
3233
} catch (e: IllegalArgumentException) {
3334
logger.log(
@@ -56,9 +57,9 @@ internal class PathUtils(
5657
return "#$alphaValue$rgbColor"
5758
}
5859

59-
@Suppress("UnsafeThirdPartyFunctionCall") // handling UnsupportedOperationException
60-
internal fun asAndroidPathSafe(path: Path): android.graphics.Path? {
60+
internal fun asAndroidPathSafe(path: Path): AndroidPath? {
6161
return try {
62+
@Suppress("UnsafeThirdPartyFunctionCall") // handling UnsupportedOperationException
6263
path.asAndroidPath()
6364
} catch (e: UnsupportedOperationException) {
6465
logger.log(
@@ -144,28 +145,18 @@ internal class PathUtils(
144145
checkmarkColor: Int
145146
): Bitmap? {
146147
val canvas = canvasWrapper.createCanvas(bitmap) ?: return null
147-
drawCanvasBackground(canvas, fillColor)
148-
drawCanvasForeground(canvas, scaledPath, checkmarkColor)
149-
return bitmap
150-
}
151148

152-
private fun drawCanvasBackground(
153-
canvas: Canvas,
154-
fillColor: Int
155-
) {
149+
// draw the background
156150
canvas.drawColor(fillColor)
157-
}
158151

159-
private fun drawCanvasForeground(
160-
canvas: Canvas,
161-
path: Path,
162-
checkmarkColor: Int
163-
) {
164-
drawPathToBitmap(checkmarkColor, path, canvas)
152+
// draw the checkmark
153+
drawPathToBitmap(checkmarkColor, scaledPath, canvas)
154+
155+
return bitmap
165156
}
166157

167158
@Suppress("UnsafeThirdPartyFunctionCall") // handling IllegalArgumentException
168-
private fun drawPathSafe(canvas: Canvas?, path: android.graphics.Path, paint: Paint) {
159+
private fun drawPathSafe(canvas: Canvas?, path: AndroidPath, paint: Paint) {
169160
try {
170161
canvas?.drawPath(path, paint)
171162
} catch (e: IllegalArgumentException) {

features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/ReflectionUtils.kt

+30
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
package com.datadog.android.sessionreplay.compose.internal.utils
88

99
import android.view.View
10+
import androidx.compose.animation.core.AnimationState
1011
import androidx.compose.runtime.Composition
1112
import androidx.compose.ui.Modifier
1213
import androidx.compose.ui.graphics.Color
1314
import androidx.compose.ui.graphics.ColorProducer
15+
import androidx.compose.ui.graphics.Path
1416
import androidx.compose.ui.graphics.Shape
1517
import androidx.compose.ui.layout.Placeable
1618
import androidx.compose.ui.semantics.SemanticsNode
@@ -48,6 +50,10 @@ internal class ReflectionUtils {
4850
return ComposeReflection.AndroidComposeViewClass?.isInstance(any) == true
4951
}
5052

53+
fun isDrawBehindElementClass(modifier: Modifier): Boolean {
54+
return ComposeReflection.DrawBehindElementClass?.isInstance(modifier) == true
55+
}
56+
5157
fun getOwner(composition: Composition): Any? {
5258
return ComposeReflection.OwnerField?.getSafe(composition)
5359
}
@@ -97,4 +103,28 @@ internal class ReflectionUtils {
97103
fun getClipShape(modifier: Modifier): Shape? {
98104
return ComposeReflection.ClipShapeField?.getSafe(modifier) as? Shape
99105
}
106+
107+
fun getOnDraw(modifier: Modifier): Any? {
108+
return ComposeReflection.OnDrawField?.getSafe(modifier)
109+
}
110+
111+
fun getBoxColor(onDrawInstance: Any): AnimationState<*, *>? {
112+
return ComposeReflection.BoxColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
113+
}
114+
115+
fun getCheckColor(onDrawInstance: Any): AnimationState<*, *>? {
116+
return ComposeReflection.CheckColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
117+
}
118+
119+
fun getBorderColor(onDrawInstance: Any): AnimationState<*, *>? {
120+
return ComposeReflection.BorderColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
121+
}
122+
123+
fun getCheckCache(onDrawInstance: Any): Any? {
124+
return ComposeReflection.CheckCacheField?.getSafe(onDrawInstance)
125+
}
126+
127+
fun getCheckPath(checkCache: Any): Path? {
128+
return ComposeReflection.CheckPathField?.getSafe(checkCache) as? Path
129+
}
100130
}

features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt

+10-11
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package com.datadog.android.sessionreplay.compose.internal.utils
88

99
import android.view.View
10-
import androidx.compose.animation.core.AnimationState
1110
import androidx.compose.foundation.shape.RoundedCornerShape
1211
import androidx.compose.ui.Modifier
1312
import androidx.compose.ui.geometry.Size
@@ -21,7 +20,6 @@ import androidx.compose.ui.text.TextLayoutInput
2120
import androidx.compose.ui.text.TextLayoutResult
2221
import androidx.compose.ui.unit.Density
2322
import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.TextLayoutInfo
24-
import com.datadog.android.sessionreplay.compose.internal.reflection.ComposeReflection
2523
import com.datadog.android.sessionreplay.utils.GlobalBounds
2624

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

109107
internal fun resolveCheckPath(semanticsNode: SemanticsNode): Path? =
110108
resolveOnDrawInstance(semanticsNode)?.let { onDraw ->
111-
ComposeReflection.CheckCacheField?.getSafe(onDraw)?.let { checkCache ->
112-
ComposeReflection.CheckPathField?.getSafe(checkCache) as? Path
109+
reflectionUtils.getCheckCache(onDraw)?.let { checkCache ->
110+
reflectionUtils.getCheckPath(checkCache)
113111
}
114112
}
115113

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

247245
return drawBehindElement?.let {
248-
ComposeReflection.OnDrawField?.getSafe(drawBehindElement)
246+
reflectionUtils.getOnDraw(it)
249247
}
250248
}
251249

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

255-
val checkmarkColor: AnimationState<*, *>? = onDrawInstance?.let {
253+
val color = onDrawInstance?.let {
256254
when (fieldType) {
257255
CheckmarkFieldType.FILL_COLOR -> {
258-
ComposeReflection.BoxColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
256+
reflectionUtils.getBoxColor(onDrawInstance)
259257
}
260258
CheckmarkFieldType.CHECKMARK_COLOR -> {
261-
ComposeReflection.CheckColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
259+
reflectionUtils.getCheckColor(onDrawInstance)
262260
}
263261
CheckmarkFieldType.BORDER_COLOR -> {
264-
ComposeReflection.BorderColorField?.getSafe(onDrawInstance) as? AnimationState<*, *>
262+
reflectionUtils.getBorderColor(onDrawInstance)
265263
}
266264
}
267265
}
268266

269-
val result = (checkmarkColor?.value as? androidx.compose.ui.graphics.Color)?.value
267+
val result = (color?.value as? Color)
268+
?.value
270269

271270
return result?.toLong()
272271
}

0 commit comments

Comments
 (0)