Skip to content

Commit 1748004

Browse files
Merge branch 'main' into andrey/sr-readme
2 parents 587aba4 + a83f828 commit 1748004

File tree

11 files changed

+247
-130
lines changed

11 files changed

+247
-130
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"go": "0.4.0",
33
"sdk/@launchdarkly/observability": "0.4.9",
4-
"sdk/@launchdarkly/observability-android": "0.18.0",
4+
"sdk/@launchdarkly/observability-android": "0.19.0",
55
"sdk/@launchdarkly/observability-dotnet": "0.3.0",
66
"sdk/@launchdarkly/observability-node": "0.3.1",
77
"sdk/@launchdarkly/observability-python": "0.1.1",

e2e/android/app/src/main/java/com/example/androidobservability/masking/XMLUserFormActivity.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.example.androidobservability.masking
22

3+
import android.animation.ObjectAnimator
4+
import android.animation.ValueAnimator
35
import android.os.Bundle
6+
import android.view.View
7+
import android.view.animation.LinearInterpolator
48
import android.widget.Button
59
import android.widget.EditText
610
import android.widget.Toast
@@ -13,6 +17,13 @@ class XMLUserFormActivity : ComponentActivity() {
1317
super.onCreate(savedInstanceState)
1418
setContentView(R.layout.activity_user_form)
1519

20+
val creditCardSection = findViewById<View>(R.id.credit_card_section)
21+
ObjectAnimator.ofFloat(creditCardSection, "rotationY", 0f, 360f).apply {
22+
duration = 2000
23+
repeatCount = ValueAnimator.INFINITE
24+
interpolator = LinearInterpolator()
25+
}.start()
26+
1627
val inputCardholderName = findViewById<EditText>(R.id.input_cardholder_name)
1728
val inputCardNumber = findViewById<EditText>(R.id.input_card_number)
1829
val inputExpiry = findViewById<EditText>(R.id.input_expiry)

e2e/android/app/src/main/res/layout/activity_user_form.xml

Lines changed: 85 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -90,101 +90,108 @@
9090
</LinearLayout>
9191

9292
<!-- Credit Card Section -->
93-
<TextView
94-
android:layout_width="wrap_content"
95-
android:layout_height="wrap_content"
96-
android:text="Credit Card Information"
97-
android:textStyle="bold"
98-
android:paddingBottom="8dp" />
99-
100-
<TextView
101-
android:layout_width="wrap_content"
102-
android:layout_height="wrap_content"
103-
android:text="Cardholder Name" />
104-
105-
<EditText
106-
android:id="@+id/input_cardholder_name"
93+
<LinearLayout
94+
android:id="@+id/credit_card_section"
10795
android:layout_width="match_parent"
10896
android:layout_height="wrap_content"
109-
android:hint="e.g., Jane Appleseed"
110-
android:inputType="textPersonName"
111-
android:paddingBottom="8dp" />
112-
113-
<TextView
114-
android:layout_width="wrap_content"
115-
android:layout_height="wrap_content"
116-
android:text="Card Number" />
97+
android:orientation="vertical">
11798

118-
<EditText
119-
android:id="@+id/input_card_number"
120-
android:layout_width="match_parent"
121-
android:layout_height="wrap_content"
122-
android:hint="1234 5678 9012 3456"
123-
android:inputType="number"
124-
android:maxLength="19"
125-
android:paddingBottom="8dp" />
99+
<TextView
100+
android:layout_width="wrap_content"
101+
android:layout_height="wrap_content"
102+
android:text="Credit Card Information"
103+
android:textStyle="bold"
104+
android:paddingBottom="8dp" />
126105

127-
<LinearLayout
128-
android:layout_width="match_parent"
129-
android:layout_height="wrap_content"
130-
android:orientation="horizontal"
131-
android:weightSum="2"
132-
android:paddingBottom="8dp">
106+
<TextView
107+
android:layout_width="wrap_content"
108+
android:layout_height="wrap_content"
109+
android:text="Cardholder Name" />
133110

134-
<LinearLayout
135-
android:layout_width="0dp"
111+
<EditText
112+
android:id="@+id/input_cardholder_name"
113+
android:layout_width="match_parent"
136114
android:layout_height="wrap_content"
137-
android:layout_weight="1"
138-
android:orientation="vertical"
139-
android:paddingRight="8dp">
115+
android:hint="e.g., Jane Appleseed"
116+
android:inputType="textPersonName"
117+
android:paddingBottom="8dp" />
140118

141-
<TextView
142-
android:layout_width="wrap_content"
143-
android:layout_height="wrap_content"
144-
android:text="Expiry (MM/YY)" />
119+
<TextView
120+
android:layout_width="wrap_content"
121+
android:layout_height="wrap_content"
122+
android:text="Card Number" />
145123

146-
<EditText
147-
android:id="@+id/input_expiry"
148-
android:layout_width="match_parent"
149-
android:layout_height="wrap_content"
150-
android:hint="MM/YY"
151-
android:inputType="number" />
152-
</LinearLayout>
124+
<EditText
125+
android:id="@+id/input_card_number"
126+
android:layout_width="match_parent"
127+
android:layout_height="wrap_content"
128+
android:hint="1234 5678 9012 3456"
129+
android:inputType="number"
130+
android:maxLength="19"
131+
android:paddingBottom="8dp" />
153132

154133
<LinearLayout
155-
android:layout_width="0dp"
134+
android:layout_width="match_parent"
156135
android:layout_height="wrap_content"
157-
android:layout_weight="1"
158-
android:orientation="vertical"
159-
android:paddingLeft="8dp">
136+
android:orientation="horizontal"
137+
android:weightSum="2"
138+
android:paddingBottom="8dp">
160139

161-
<TextView
162-
android:layout_width="wrap_content"
140+
<LinearLayout
141+
android:layout_width="0dp"
163142
android:layout_height="wrap_content"
164-
android:text="CVV" />
165-
166-
<EditText
167-
android:id="@+id/input_cvv"
168-
android:layout_width="match_parent"
143+
android:layout_weight="1"
144+
android:orientation="vertical"
145+
android:paddingRight="8dp">
146+
147+
<TextView
148+
android:layout_width="wrap_content"
149+
android:layout_height="wrap_content"
150+
android:text="Expiry (MM/YY)" />
151+
152+
<EditText
153+
android:id="@+id/input_expiry"
154+
android:layout_width="match_parent"
155+
android:layout_height="wrap_content"
156+
android:hint="MM/YY"
157+
android:inputType="number" />
158+
</LinearLayout>
159+
160+
<LinearLayout
161+
android:layout_width="0dp"
169162
android:layout_height="wrap_content"
170-
android:hint="123"
171-
android:inputType="numberPassword"
172-
android:maxLength="4" />
163+
android:layout_weight="1"
164+
android:orientation="vertical"
165+
android:paddingLeft="8dp">
166+
167+
<TextView
168+
android:layout_width="wrap_content"
169+
android:layout_height="wrap_content"
170+
android:text="CVV" />
171+
172+
<EditText
173+
android:id="@+id/input_cvv"
174+
android:layout_width="match_parent"
175+
android:layout_height="wrap_content"
176+
android:hint="123"
177+
android:inputType="numberPassword"
178+
android:maxLength="4" />
179+
</LinearLayout>
173180
</LinearLayout>
174-
</LinearLayout>
175181

176-
<TextView
177-
android:layout_width="wrap_content"
178-
android:layout_height="wrap_content"
179-
android:text="ZIP / Postal Code" />
182+
<TextView
183+
android:layout_width="wrap_content"
184+
android:layout_height="wrap_content"
185+
android:text="ZIP / Postal Code" />
180186

181-
<EditText
182-
android:id="@+id/input_zip_code"
183-
android:layout_width="match_parent"
184-
android:layout_height="wrap_content"
185-
android:hint="e.g., 94105"
186-
android:inputType="textPostalAddress"
187-
android:paddingBottom="8dp" />
187+
<EditText
188+
android:id="@+id/input_zip_code"
189+
android:layout_width="match_parent"
190+
android:layout_height="wrap_content"
191+
android:hint="e.g., 94105"
192+
android:inputType="textPostalAddress"
193+
android:paddingBottom="8dp" />
194+
</LinearLayout>
188195

189196
<Button
190197
android:id="@+id/button_save_card"

sdk/@launchdarkly/observability-android/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [0.19.0](https://github.com/launchdarkly/observability-sdk/compare/launchdarkly-observability-android-0.18.0...launchdarkly-observability-android-0.19.0) (2025-12-04)
4+
5+
6+
### Features
7+
8+
* take transformed coordinates, which are more precise in animation ([#309](https://github.com/launchdarkly/observability-sdk/issues/309)) ([5d669d4](https://github.com/launchdarkly/observability-sdk/commit/5d669d49a7d412b4edce8e5f5bdc7728243bd2c3))
9+
310
## [0.18.0](https://github.com/launchdarkly/observability-sdk/compare/launchdarkly-observability-android-0.17.0...launchdarkly-observability-android-0.18.0) (2025-12-04)
411

512

sdk/@launchdarkly/observability-android/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
android.useAndroidX=true
55

66
#x-release-please-start-version
7-
version=0.18.0
7+
version=0.19.0
88
#x-release-please-end

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/capture/CaptureSource.kt

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import android.view.Window
1616
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION
1717
import android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION
1818
import androidx.annotation.RequiresApi
19+
import android.graphics.Path
1920
import com.launchdarkly.logging.LDLogger
2021
import com.launchdarkly.observability.coroutines.DispatcherProviderHolder
2122
import com.launchdarkly.observability.replay.masking.MaskMatcher
@@ -86,7 +87,10 @@ class CaptureSource(
8687
}
8788
}
8889
}
89-
90+
91+
val timestamp = System.currentTimeMillis()
92+
val session = sessionManager.getSessionId()
93+
9094
val windowsEntries = windowInspector.appWindows()
9195
if (windowsEntries.isEmpty()) {
9296
return@withContext null
@@ -104,8 +108,6 @@ class CaptureSource(
104108
// TODO: O11Y-625 - see if holding bitmap is more efficient than base64 encoding immediately after compression
105109
// TODO: O11Y-628 - use captureQuality option for scaling and adjust this bitmap accordingly, may need to investigate power of 2 rounding for performance
106110
// Create a bitmap with the window dimensions
107-
val timestamp = System.currentTimeMillis()
108-
val session = sessionManager.getSessionId()
109111
val baseResult = captureViewResult(baseWindowEntry) ?: return@withContext null
110112

111113
// capture rest of views on top of base
@@ -169,6 +171,7 @@ class CaptureSource(
169171
private suspend fun captureViewResult(windowEntry: WindowEntry): CaptureResult? {
170172
val bitmap = captureViewBitmap(windowEntry) ?: return null
171173
val masks = maskCollector.collectMasks(windowEntry.rootView, maskMatchers)
174+
172175
return CaptureResult(windowEntry, bitmap, masks)
173176
}
174177

@@ -281,14 +284,31 @@ class CaptureSource(
281284
* @param masks areas that will be masked
282285
*/
283286
private fun drawMasks(canvas: Canvas, masks: List<Mask>) {
287+
val path = Path()
284288
masks.forEach { mask ->
285-
val integerRect = Rect(
286-
mask.rect.left.toInt(),
287-
mask.rect.top.toInt(),
288-
mask.rect.right.toInt(),
289-
mask.rect.bottom.toInt()
290-
)
291-
canvas.drawRect(integerRect, maskPaint)
289+
drawMask(mask, path, canvas, maskPaint)
290+
}
291+
}
292+
293+
private val maskIntRect = Rect()
294+
private fun drawMask(mask: Mask, path: Path, canvas: Canvas, paint: Paint) {
295+
if (mask.points != null) {
296+
val pts = mask.points
297+
298+
path.reset()
299+
path.moveTo(pts[0], pts[1])
300+
path.lineTo(pts[2], pts[3])
301+
path.lineTo(pts[4], pts[5])
302+
path.lineTo(pts[6], pts[7])
303+
path.close()
304+
305+
canvas.drawPath(path, paint)
306+
} else {
307+
maskIntRect.left = mask.rect.left.toInt()
308+
maskIntRect.top = mask.rect.top.toInt()
309+
maskIntRect.right = mask.rect.right.toInt()
310+
maskIntRect.bottom = mask.rect.bottom.toInt()
311+
canvas.drawRect(maskIntRect, paint)
292312
}
293313
}
294314
}

sdk/@launchdarkly/observability-android/lib/src/main/kotlin/com/launchdarkly/observability/replay/masking/ComposeMaskTarget.kt

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.launchdarkly.observability.replay.masking
22

3+
import android.os.Build
34
import android.view.View
5+
import androidx.compose.ui.geometry.Offset
46
import androidx.compose.ui.graphics.toAndroidRectF
57
import androidx.compose.ui.platform.AbstractComposeView
68
import androidx.compose.ui.semantics.SemanticsNode
@@ -73,12 +75,14 @@ data class ComposeMaskTarget(
7375
return config.contains(SemanticsProperties.Text)
7476
}
7577

76-
override fun mask(): Mask? {
78+
override fun mask(context: MaskContext): Mask? {
7779
val rect = boundsInWindow.toAndroidRectF()
7880
if (rect.width() <= 0f || rect.height() <= 0f) {
7981
return null
8082
}
81-
return Mask(boundsInWindow.toAndroidRectF(), view.id)
83+
84+
val points: FloatArray? = points(context)
85+
return Mask(boundsInWindow.toAndroidRectF(), view.id, points)
8286
}
8387

8488
override fun hasLDMask(): Boolean {
@@ -103,6 +107,41 @@ data class ComposeMaskTarget(
103107
return hasSensitiveDescription
104108
}
105109

110+
// return 4 points of polygon under transformations
111+
private fun points(context: MaskContext): FloatArray? {
112+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
113+
return null
114+
}
115+
116+
val coordinates = rootNode.layoutInfo.coordinates
117+
if (!coordinates.isAttached) {
118+
return null
119+
}
120+
121+
val size = coordinates.size
122+
if (size.width <= 0 || size.height <= 0) {
123+
return null
124+
}
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()))
130+
131+
val pts = floatArrayOf(
132+
t1.x, t1.y,
133+
t2.x, t2.y,
134+
t3.x, t3.y,
135+
t4.x, t4.y
136+
)
137+
138+
for (i in pts.indices step 2) {
139+
pts[i] -= context.rootX
140+
pts[i + 1] -= context.rootY
141+
}
142+
143+
return pts
144+
}
106145
}
107146

108147

0 commit comments

Comments
 (0)