You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: Android double masking with frame drop (#342)
## Summary
Introduces double masking and if number of masks changed the frame being
dropped.
<img width="504" height="531" alt="image"
src="https://github.com/user-attachments/assets/99c1ec8a-970d-40bf-8dd9-ba86df9ecc38"
/>
## How did you test this change?
<!--
Frontend - Leave a screencast or a screenshot to visually describe the
changes.
-->
## Are there any deployment considerations?
<!--
Backend - Do we need to consider migrations or backfilling data?
-->
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Introduces double-masking to stabilize sensitive-region masking and
avoid flicker by dropping unstable frames.
>
> - Capture: collect masks for each window both before and after a
synced frame (`CaptureSource`), merge via `MaskApplier.mergeMasksMap`
with position tolerance; if counts/IDs shift or movement exceeds
threshold, drop the frame
> - Rendering: replace inline mask drawing with `MaskApplier.drawMasks`;
compose layered windows onto base bitmap, recycle intermediate bitmaps,
and improve PixelCopy error handling and cleanup
> - Window selection: `pickBaseWindow` now returns index; capture
proceeds from base through overlays; maintains tiled signature dedupe
> - Mask targets: use `localToWindow` in Compose masking; minor matrix
reuse comment in native target
> - E2E demo: add `.ldMask()` to ZIP and CVV fields; minor cleanup in
Compose/XML samples
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5bff2cd. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Agustin Grognetti <agrognetti@launchdarkly.com>
Copy file name to clipboardExpand all lines: sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/capture/CaptureSource.kt
val baseWindowEntry = pickBaseWindow(windowsEntries) ?:return@withContext null
95
+
val baseIndex = pickBaseWindow(windowsEntries) ?:return@withContext null
96
+
val baseWindowEntry = windowsEntries[baseIndex]
100
97
val rect = baseWindowEntry.rect()
101
98
102
99
// protect against race condition where decor view has no size
@@ -107,72 +104,130 @@ class CaptureSource(
107
104
// TODO: O11Y-625 - optimize memory allocations
108
105
// TODO: O11Y-625 - see if holding bitmap is more efficient than base64 encoding immediately after compression
109
106
// TODO: O11Y-628 - use captureQuality option for scaling and adjust this bitmap accordingly, may need to investigate power of 2 rounding for performance
110
-
// Create a bitmap with the window dimensions
111
-
val baseResult = captureViewResult(baseWindowEntry) ?:return@withContext null
112
-
113
-
// capture rest of views on top of base
114
-
val pairs = mutableListOf<CaptureResult>()
115
-
var afterBase =false
116
-
for (windowEntry in windowsEntries) {
117
-
if (afterBase) {
118
-
captureViewResult(windowEntry)?.let { result ->
119
-
pairs.add(result)
107
+
108
+
val capturingWindowEntries = windowsEntries.subList(baseIndex, windowsEntries.size)
109
+
110
+
val beforeMasks = collectMasks(capturingWindowEntries)
111
+
112
+
val captureResults:MutableList<CaptureResult?> =MutableList(capturingWindowEntries.size) { null }
113
+
try {
114
+
var captured =0
115
+
for (i in capturingWindowEntries.indices) {
116
+
val windowEntry = capturingWindowEntries[i]
117
+
val captureResult = captureViewResult(windowEntry)
Copy file name to clipboardExpand all lines: sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/masking/ComposeMaskTarget.kt
+4-4Lines changed: 4 additions & 4 deletions
Original file line number
Diff line number
Diff line change
@@ -123,10 +123,10 @@ data class ComposeMaskTarget(
123
123
returnnull
124
124
}
125
125
126
-
val t1 = coordinates.localToScreen(Offset(0f, 0f))
127
-
val t2 = coordinates.localToScreen(Offset(size.width.toFloat(), 0f))
128
-
val t3 = coordinates.localToScreen(Offset(size.width.toFloat(), size.height.toFloat()))
129
-
val t4 = coordinates.localToScreen(Offset(0f, size.height.toFloat()))
126
+
val t1 = coordinates.localToWindow(Offset(0f, 0f))
127
+
val t2 = coordinates.localToWindow(Offset(size.width.toFloat(), 0f))
128
+
val t3 = coordinates.localToWindow(Offset(size.width.toFloat(), size.height.toFloat()))
129
+
val t4 = coordinates.localToWindow(Offset(0f, size.height.toFloat()))
0 commit comments