Skip to content

Commit 2383221

Browse files
authored
Merge pull request #2354 from DataDog/yl/ga/fix-scrolling-freezing
RUM-6897: Fix scrolling issue & Add DrawableCopier interface for drawable copying for image wireframe
2 parents b68f6b3 + 73490b8 commit 2383221

File tree

15 files changed

+256
-122
lines changed

15 files changed

+256
-122
lines changed

features/dd-sdk-android-session-replay/api/apiSurface

+5-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ enum com.datadog.android.sessionreplay.TextAndInputPrivacy : PrivacyLevel
5050
enum com.datadog.android.sessionreplay.TouchPrivacy : PrivacyLevel
5151
- SHOW
5252
- HIDE
53+
class com.datadog.android.sessionreplay.internal.recorder.resources.DefaultDrawableCopier : DrawableCopier
54+
override fun copy(android.graphics.drawable.Drawable, android.content.res.Resources): android.graphics.drawable.Drawable?
55+
interface com.datadog.android.sessionreplay.internal.recorder.resources.DrawableCopier
56+
fun copy(android.graphics.drawable.Drawable, android.content.res.Resources): android.graphics.drawable.Drawable?
5357
data class com.datadog.android.sessionreplay.recorder.MappingContext
5458
constructor(SystemInformation, com.datadog.android.sessionreplay.utils.ImageWireframeHelper, com.datadog.android.sessionreplay.TextAndInputPrivacy, com.datadog.android.sessionreplay.ImagePrivacy, Boolean = false)
5559
interface com.datadog.android.sessionreplay.recorder.OptionSelectorDetector
@@ -106,7 +110,7 @@ interface com.datadog.android.sessionreplay.utils.DrawableToColorMapper
106110
data class com.datadog.android.sessionreplay.utils.GlobalBounds
107111
constructor(Long, Long, Long, Long)
108112
interface com.datadog.android.sessionreplay.utils.ImageWireframeHelper
109-
fun createImageWireframe(android.view.View, com.datadog.android.sessionreplay.ImagePrivacy, Int, Long, Long, Int, Int, Boolean, android.graphics.drawable.Drawable, AsyncJobStatusCallback, com.datadog.android.sessionreplay.model.MobileSegment.WireframeClip? = null, com.datadog.android.sessionreplay.model.MobileSegment.ShapeStyle? = null, com.datadog.android.sessionreplay.model.MobileSegment.ShapeBorder? = null, String? = DRAWABLE_CHILD_NAME): com.datadog.android.sessionreplay.model.MobileSegment.Wireframe?
113+
fun createImageWireframe(android.view.View, com.datadog.android.sessionreplay.ImagePrivacy, Int, Long, Long, Int, Int, Boolean, android.graphics.drawable.Drawable, com.datadog.android.sessionreplay.internal.recorder.resources.DrawableCopier = DefaultDrawableCopier(), AsyncJobStatusCallback, com.datadog.android.sessionreplay.model.MobileSegment.WireframeClip? = null, com.datadog.android.sessionreplay.model.MobileSegment.ShapeStyle? = null, com.datadog.android.sessionreplay.model.MobileSegment.ShapeBorder? = null, String? = DRAWABLE_CHILD_NAME): com.datadog.android.sessionreplay.model.MobileSegment.Wireframe?
110114
fun createCompoundDrawableWireframes(android.widget.TextView, com.datadog.android.sessionreplay.recorder.MappingContext, Int, AsyncJobStatusCallback): MutableList<com.datadog.android.sessionreplay.model.MobileSegment.Wireframe>
111115
companion object
112116
open class com.datadog.android.sessionreplay.utils.LegacyDrawableToColorMapper : DrawableToColorMapper

features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api

+11-2
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ public final class com/datadog/android/sessionreplay/TouchPrivacy : java/lang/En
106106
public static fun values ()[Lcom/datadog/android/sessionreplay/TouchPrivacy;
107107
}
108108

109+
public final class com/datadog/android/sessionreplay/internal/recorder/resources/DefaultDrawableCopier : com/datadog/android/sessionreplay/internal/recorder/resources/DrawableCopier {
110+
public fun <init> ()V
111+
public fun copy (Landroid/graphics/drawable/Drawable;Landroid/content/res/Resources;)Landroid/graphics/drawable/Drawable;
112+
}
113+
114+
public abstract interface class com/datadog/android/sessionreplay/internal/recorder/resources/DrawableCopier {
115+
public abstract fun copy (Landroid/graphics/drawable/Drawable;Landroid/content/res/Resources;)Landroid/graphics/drawable/Drawable;
116+
}
117+
109118
public final class com/datadog/android/sessionreplay/model/MobileSegment {
110119
public static final field Companion Lcom/datadog/android/sessionreplay/model/MobileSegment$Companion;
111120
public fun <init> (Lcom/datadog/android/sessionreplay/model/MobileSegment$Application;Lcom/datadog/android/sessionreplay/model/MobileSegment$Session;Lcom/datadog/android/sessionreplay/model/MobileSegment$View;JJJLjava/lang/Long;Ljava/lang/Boolean;Lcom/datadog/android/sessionreplay/model/MobileSegment$Source;Ljava/util/List;)V
@@ -1553,14 +1562,14 @@ public final class com/datadog/android/sessionreplay/utils/GlobalBounds {
15531562
public abstract interface class com/datadog/android/sessionreplay/utils/ImageWireframeHelper {
15541563
public static final field Companion Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper$Companion;
15551564
public abstract fun createCompoundDrawableWireframes (Landroid/widget/TextView;Lcom/datadog/android/sessionreplay/recorder/MappingContext;ILcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;)Ljava/util/List;
1556-
public abstract fun createImageWireframe (Landroid/view/View;Lcom/datadog/android/sessionreplay/ImagePrivacy;IJJIIZLandroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;Lcom/datadog/android/sessionreplay/model/MobileSegment$WireframeClip;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeStyle;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeBorder;Ljava/lang/String;)Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe;
1565+
public abstract fun createImageWireframe (Landroid/view/View;Lcom/datadog/android/sessionreplay/ImagePrivacy;IJJIIZLandroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/internal/recorder/resources/DrawableCopier;Lcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;Lcom/datadog/android/sessionreplay/model/MobileSegment$WireframeClip;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeStyle;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeBorder;Ljava/lang/String;)Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe;
15571566
}
15581567

15591568
public final class com/datadog/android/sessionreplay/utils/ImageWireframeHelper$Companion {
15601569
}
15611570

15621571
public final class com/datadog/android/sessionreplay/utils/ImageWireframeHelper$DefaultImpls {
1563-
public static synthetic fun createImageWireframe$default (Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Landroid/view/View;Lcom/datadog/android/sessionreplay/ImagePrivacy;IJJIIZLandroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;Lcom/datadog/android/sessionreplay/model/MobileSegment$WireframeClip;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeStyle;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeBorder;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe;
1572+
public static synthetic fun createImageWireframe$default (Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Landroid/view/View;Lcom/datadog/android/sessionreplay/ImagePrivacy;IJJIIZLandroid/graphics/drawable/Drawable;Lcom/datadog/android/sessionreplay/internal/recorder/resources/DrawableCopier;Lcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;Lcom/datadog/android/sessionreplay/model/MobileSegment$WireframeClip;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeStyle;Lcom/datadog/android/sessionreplay/model/MobileSegment$ShapeBorder;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/sessionreplay/model/MobileSegment$Wireframe;
15641573
}
15651574

15661575
public class com/datadog/android/sessionreplay/utils/LegacyDrawableToColorMapper : com/datadog/android/sessionreplay/utils/DrawableToColorMapper {

features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ImageViewMapper.kt

+18-21
Original file line numberDiff line numberDiff line change
@@ -57,28 +57,25 @@ internal class ImageViewMapper(
5757
val contentYPosInDp = contentRect.top.densityNormalized(density).toLong()
5858
val contentWidthPx = contentRect.width()
5959
val contentHeightPx = contentRect.height()
60-
val contentDrawable = drawable.constantState?.newDrawable(resources)
6160

62-
if (contentDrawable != null) {
63-
// resolve foreground
64-
mappingContext.imageWireframeHelper.createImageWireframe(
65-
view = view,
66-
imagePrivacy = mappingContext.imagePrivacy,
67-
currentWireframeIndex = wireframes.size,
68-
x = contentXPosInDp,
69-
y = contentYPosInDp,
70-
width = contentWidthPx,
71-
height = contentHeightPx,
72-
usePIIPlaceholder = true,
73-
drawable = contentDrawable,
74-
asyncJobStatusCallback = asyncJobStatusCallback,
75-
clipping = clipping,
76-
shapeStyle = null,
77-
border = null,
78-
prefix = ImageWireframeHelper.DRAWABLE_CHILD_NAME
79-
)?.let {
80-
wireframes.add(it)
81-
}
61+
// resolve foreground
62+
mappingContext.imageWireframeHelper.createImageWireframe(
63+
view = view,
64+
imagePrivacy = mappingContext.imagePrivacy,
65+
currentWireframeIndex = wireframes.size,
66+
x = contentXPosInDp,
67+
y = contentYPosInDp,
68+
width = contentWidthPx,
69+
height = contentHeightPx,
70+
usePIIPlaceholder = true,
71+
drawable = drawable,
72+
asyncJobStatusCallback = asyncJobStatusCallback,
73+
clipping = clipping,
74+
shapeStyle = null,
75+
border = null,
76+
prefix = ImageWireframeHelper.DRAWABLE_CHILD_NAME
77+
)?.let {
78+
wireframes.add(it)
8279
}
8380

8481
return wireframes

features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SwitchCompatMapper.kt

+60-37
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66

77
package com.datadog.android.sessionreplay.internal.recorder.mapper
88

9+
import android.content.res.Resources
10+
import android.graphics.drawable.Drawable
911
import androidx.annotation.UiThread
1012
import androidx.appcompat.widget.SwitchCompat
1113
import com.datadog.android.api.InternalLogger
1214
import com.datadog.android.sessionreplay.internal.recorder.densityNormalized
15+
import com.datadog.android.sessionreplay.internal.recorder.resources.DrawableCopier
1316
import com.datadog.android.sessionreplay.model.MobileSegment
1417
import com.datadog.android.sessionreplay.recorder.MappingContext
1518
import com.datadog.android.sessionreplay.recorder.mapper.TextViewMapper
@@ -71,28 +74,35 @@ internal open class SwitchCompatMapper(
7174
mappingContext.systemInformation.screenDensity
7275
)
7376
return trackBounds?.let {
74-
return view.trackDrawable.constantState?.newDrawable(view.resources)?.apply {
75-
setState(view.trackDrawable.state)
76-
bounds = view.trackDrawable.bounds
77-
view.trackTintList?.let {
78-
setTintList(it)
77+
val trackDrawable = view.trackDrawable
78+
val drawableCopier = object : DrawableCopier {
79+
override fun copy(originalDrawable: Drawable, resources: Resources): Drawable? {
80+
return originalDrawable.constantState?.newDrawable(view.resources)?.apply {
81+
setState(view.trackDrawable.state)
82+
bounds = view.trackDrawable.bounds
83+
view.trackTintList?.let {
84+
setTintList(it)
85+
}
86+
}
7987
}
80-
}?.let { drawable ->
81-
mappingContext.imageWireframeHelper.createImageWireframe(
82-
view = view,
83-
imagePrivacy = mapInputPrivacyToImagePrivacy(mappingContext.textAndInputPrivacy),
84-
currentWireframeIndex = prevIndex + 1,
85-
x = trackBounds.x.densityNormalized(mappingContext.systemInformation.screenDensity).toLong(),
86-
y = trackBounds.y.densityNormalized(mappingContext.systemInformation.screenDensity).toLong(),
87-
width = trackBounds.width,
88-
height = trackBounds.height,
89-
drawable = drawable,
90-
shapeStyle = null,
91-
border = null,
92-
usePIIPlaceholder = true,
93-
asyncJobStatusCallback = asyncJobStatusCallback
94-
)
9588
}
89+
return mappingContext.imageWireframeHelper.createImageWireframe(
90+
view = view,
91+
imagePrivacy = mapInputPrivacyToImagePrivacy(mappingContext.textAndInputPrivacy),
92+
currentWireframeIndex = prevIndex + 1,
93+
x = it.x.densityNormalized(mappingContext.systemInformation.screenDensity)
94+
.toLong(),
95+
y = it.y.densityNormalized(mappingContext.systemInformation.screenDensity)
96+
.toLong(),
97+
width = it.width,
98+
height = it.height,
99+
drawable = trackDrawable,
100+
drawableCopier = drawableCopier,
101+
shapeStyle = null,
102+
border = null,
103+
usePIIPlaceholder = true,
104+
asyncJobStatusCallback = asyncJobStatusCallback
105+
)
96106
}
97107
}
98108

@@ -107,25 +117,38 @@ internal open class SwitchCompatMapper(
107117
mappingContext.systemInformation.screenDensity
108118
)
109119

110-
return view.thumbDrawable?.let { drawable ->
111-
thumbBounds?.let { thumbBounds ->
112-
mappingContext.imageWireframeHelper.createImageWireframe(
113-
view = view,
114-
imagePrivacy = mapInputPrivacyToImagePrivacy(mappingContext.textAndInputPrivacy),
115-
currentWireframeIndex = prevIndex + 1,
116-
x = thumbBounds.x.densityNormalized(mappingContext.systemInformation.screenDensity).toLong(),
117-
y = thumbBounds.y.densityNormalized(mappingContext.systemInformation.screenDensity).toLong(),
118-
width = drawable.intrinsicWidth,
119-
height = drawable.intrinsicHeight,
120-
drawable = drawable,
121-
shapeStyle = null,
122-
border = null,
123-
usePIIPlaceholder = true,
124-
clipping = null,
125-
asyncJobStatusCallback = asyncJobStatusCallback
126-
)
120+
val thumbDrawable = view.thumbDrawable
121+
val drawableCopier = object : DrawableCopier {
122+
override fun copy(originalDrawable: Drawable, resources: Resources): Drawable? {
123+
return originalDrawable.constantState?.newDrawable(view.resources)?.apply {
124+
setState(view.thumbDrawable.state)
125+
bounds = view.thumbDrawable.bounds
126+
view.thumbTintList?.let {
127+
setTintList(it)
128+
}
129+
}
127130
}
128131
}
132+
return thumbBounds?.let {
133+
mappingContext.imageWireframeHelper.createImageWireframe(
134+
view = view,
135+
imagePrivacy = mapInputPrivacyToImagePrivacy(mappingContext.textAndInputPrivacy),
136+
currentWireframeIndex = prevIndex + 1,
137+
x = it.x.densityNormalized(mappingContext.systemInformation.screenDensity)
138+
.toLong(),
139+
y = it.y.densityNormalized(mappingContext.systemInformation.screenDensity)
140+
.toLong(),
141+
width = thumbDrawable.intrinsicWidth,
142+
height = thumbDrawable.intrinsicHeight,
143+
drawable = thumbDrawable,
144+
drawableCopier = drawableCopier,
145+
shapeStyle = null,
146+
border = null,
147+
usePIIPlaceholder = true,
148+
clipping = null,
149+
asyncJobStatusCallback = asyncJobStatusCallback
150+
)
151+
}
129152
}
130153

131154
private fun resolveThumbBounds(view: SwitchCompat, pixelsDensity: Float): GlobalBoundsInPx? {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.android.sessionreplay.internal.recorder.resources
8+
9+
import android.content.res.Resources
10+
import android.graphics.drawable.Drawable
11+
import com.datadog.android.lint.InternalApi
12+
13+
/**
14+
* Default implementation of [DrawableCopier] interface, it copies the drawable from constant state.
15+
*/
16+
@InternalApi
17+
class DefaultDrawableCopier : DrawableCopier {
18+
override fun copy(originalDrawable: Drawable, resources: Resources): Drawable? {
19+
return originalDrawable.constantState?.newDrawable(resources)
20+
}
21+
}

features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelper.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ internal class DefaultImageWireframeHelper(
4848
height: Int,
4949
usePIIPlaceholder: Boolean,
5050
drawable: Drawable,
51+
drawableCopier: DrawableCopier,
5152
asyncJobStatusCallback: AsyncJobStatusCallback,
5253
clipping: MobileSegment.WireframeClip?,
5354
shapeStyle: MobileSegment.ShapeStyle?,
@@ -137,7 +138,8 @@ internal class DefaultImageWireframeHelper(
137138
resources = resources,
138139
applicationContext = applicationContext,
139140
displayMetrics = displayMetrics,
140-
drawable = drawableProperties.drawable,
141+
originalDrawable = drawableProperties.drawable,
142+
drawableCopier = drawableCopier,
141143
drawableWidth = width,
142144
drawableHeight = height,
143145
resourceResolverCallback = object : ResourceResolverCallback {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
package com.datadog.android.sessionreplay.internal.recorder.resources
8+
9+
import android.content.res.Resources
10+
import android.graphics.drawable.Drawable
11+
import com.datadog.android.lint.InternalApi
12+
13+
/**
14+
* Interface of copying drawable to a new one.
15+
*/
16+
@InternalApi
17+
interface DrawableCopier {
18+
19+
/**
20+
* Called to copy the drawable.
21+
* @param originalDrawable the original drawable to copy
22+
* @param resources resources of the view.
23+
*
24+
* @return New copied drawable.
25+
*/
26+
fun copy(originalDrawable: Drawable, resources: Resources): Drawable?
27+
}

features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/ResourceResolver.kt

+8-5
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,15 @@ internal class ResourceResolver(
4545
resources: Resources,
4646
applicationContext: Context,
4747
displayMetrics: DisplayMetrics,
48-
drawable: Drawable,
48+
originalDrawable: Drawable,
49+
drawableCopier: DrawableCopier,
4950
drawableWidth: Int,
5051
drawableHeight: Int,
5152
resourceResolverCallback: ResourceResolverCallback
5253
) {
5354
bitmapCachesManager.registerCallbacks(applicationContext)
5455

55-
val resourceId = tryToGetResourceFromCache(drawable = drawable)
56+
val resourceId = tryToGetResourceFromCache(drawable = originalDrawable)
5657

5758
if (resourceId != null) {
5859
// if we got here it means we saw the bitmap before,
@@ -61,9 +62,11 @@ internal class ResourceResolver(
6162
return
6263
}
6364

65+
val copiedDrawable = drawableCopier.copy(originalDrawable, resources) ?: return
66+
6467
val bitmapFromDrawable =
65-
if (drawable is BitmapDrawable && shouldUseDrawableBitmap(drawable)) {
66-
drawable.bitmap // cannot be null - we already checked in shouldUseDrawableBitmap
68+
if (copiedDrawable is BitmapDrawable && shouldUseDrawableBitmap(copiedDrawable)) {
69+
copiedDrawable.bitmap // cannot be null - we already checked in shouldUseDrawableBitmap
6770
} else {
6871
null
6972
}
@@ -72,7 +75,7 @@ internal class ResourceResolver(
7275
threadPoolExecutor.executeSafe("resolveResourceId", logger) {
7376
createBitmap(
7477
resources = resources,
75-
drawable = drawable,
78+
drawable = originalDrawable,
7679
drawableWidth = drawableWidth,
7780
drawableHeight = drawableHeight,
7881
displayMetrics = displayMetrics,

0 commit comments

Comments
 (0)