Skip to content

Commit c34670b

Browse files
authored
feat: Android observability session replay masking support (#276)
## Summary Adds masking, PrivacyOptions for customizing masking. ## How did you test this change? Bench testing mostly, we have a future story for e2e tests. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds configurable session replay masking using PrivacyProfile/MaskMatcher and applies masking during capture; updates sample app to showcase sensitive inputs. > > - **SDK / Replay**: > - **Privacy & Matchers**: Introduce `PrivacyProfile` (configurable masking for text inputs, text, sensitive views) and `MaskMatcher` interface; convert profile to matcher list via `asMatchersList()`. > - **Capture Pipeline**: `CaptureSource` now accepts matcher list, syncs with `Choreographer`, traverses Compose semantics via `unmergedRootSemanticsNode`, computes sensitive rects, and masks them (`Color.GRAY`) before encoding; removes enum-based privacy and ad-hoc sensitivity check. > - **Instrumentation**: `ReplayInstrumentation` passes `options.privacyProfile.asMatchersList()` to `CaptureSource`. > - **Options**: `ReplayOptions` defaults `privacyProfile` to `PrivacyProfile()`. > - **E2E App**: > - Configure `ReplayInstrumentation` with `ReplayOptions(privacyProfile = PrivacyProfile(maskText = false))` in `BaseApplication`. > - Replace `SecondaryActivity` UI with a form containing password, address, and credit card fields to exercise masking. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ea12d8c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 1927107 commit c34670b

File tree

7 files changed

+433
-125
lines changed

7 files changed

+433
-125
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import com.launchdarkly.sdk.android.Components
99
import com.launchdarkly.sdk.android.LDClient
1010
import com.launchdarkly.sdk.android.LDConfig
1111
import com.launchdarkly.observability.plugin.Observability
12+
import com.launchdarkly.observability.replay.PrivacyProfile
1213
import com.launchdarkly.observability.replay.ReplayInstrumentation
14+
import com.launchdarkly.observability.replay.ReplayOptions
1315
import com.launchdarkly.sdk.android.LDAndroidLogging
1416
import com.launchdarkly.sdk.android.integrations.Plugin
1517
import io.opentelemetry.api.common.AttributeKey
@@ -32,7 +34,11 @@ open class BaseApplication : Application() {
3234
logAdapter = LDAndroidLogging.adapter(),
3335
// TODO: consider these being factories so that the obs plugin can pass instantiation data, log adapter
3436
instrumentations = listOf(
35-
ReplayInstrumentation()
37+
ReplayInstrumentation(
38+
options = ReplayOptions(
39+
privacyProfile = PrivacyProfile(maskText = false)
40+
)
41+
)
3642
),
3743
)
3844

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

Lines changed: 177 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,32 @@ import android.os.Bundle
44
import androidx.activity.ComponentActivity
55
import androidx.activity.compose.setContent
66
import androidx.activity.enableEdgeToEdge
7+
import androidx.compose.foundation.layout.Arrangement
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.Row
710
import androidx.compose.foundation.layout.fillMaxSize
11+
import androidx.compose.foundation.layout.fillMaxWidth
812
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.rememberScrollState
14+
import androidx.compose.foundation.text.KeyboardOptions
15+
import androidx.compose.foundation.verticalScroll
16+
import androidx.compose.material3.Button
17+
import androidx.compose.material3.Card
18+
import androidx.compose.material3.CardDefaults
19+
import androidx.compose.material3.MaterialTheme
20+
import androidx.compose.material3.OutlinedTextField
921
import androidx.compose.material3.Scaffold
1022
import androidx.compose.material3.Text
1123
import androidx.compose.runtime.Composable
24+
import androidx.compose.runtime.getValue
25+
import androidx.compose.runtime.mutableStateOf
26+
import androidx.compose.runtime.remember
27+
import androidx.compose.runtime.setValue
1228
import androidx.compose.ui.Modifier
29+
import androidx.compose.ui.text.input.KeyboardType
30+
import androidx.compose.ui.text.input.PasswordVisualTransformation
1331
import androidx.compose.ui.tooling.preview.Preview
32+
import androidx.compose.ui.unit.dp
1433
import com.example.androidobservability.ui.theme.AndroidObservabilityTheme
1534

1635
class SecondaryActivity : ComponentActivity() {
@@ -19,10 +38,12 @@ class SecondaryActivity : ComponentActivity() {
1938
enableEdgeToEdge()
2039
setContent {
2140
AndroidObservabilityTheme {
22-
Scaffold( modifier = Modifier.fillMaxSize() ) { innerPadding ->
23-
Greeting2(
24-
name = "Android",
25-
modifier = Modifier.padding(innerPadding)
41+
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
42+
UserInfoForm(
43+
modifier = Modifier
44+
.fillMaxSize()
45+
.padding(innerPadding)
46+
.padding(16.dp)
2647
)
2748
}
2849
}
@@ -31,17 +52,163 @@ class SecondaryActivity : ComponentActivity() {
3152
}
3253

3354
@Composable
34-
fun Greeting2(name: String, modifier: Modifier = Modifier) {
35-
Text(
36-
text = "Hello $name!",
55+
fun UserInfoForm(modifier: Modifier = Modifier) {
56+
var password by remember { mutableStateOf("") }
57+
var streetAddress by remember { mutableStateOf("") }
58+
var city by remember { mutableStateOf("") }
59+
var state by remember { mutableStateOf("") }
60+
var zipCode by remember { mutableStateOf("") }
61+
var creditCardNumber by remember { mutableStateOf("") }
62+
var expiryDate by remember { mutableStateOf("") }
63+
var cvv by remember { mutableStateOf("") }
64+
var cardholderName by remember { mutableStateOf("") }
65+
66+
val scrollState = rememberScrollState()
67+
68+
Column(
3769
modifier = modifier
38-
)
70+
.verticalScroll(scrollState)
71+
.fillMaxSize(),
72+
verticalArrangement = Arrangement.spacedBy(16.dp)
73+
) {
74+
Text(
75+
text = "User Information Form",
76+
style = MaterialTheme.typography.headlineMedium
77+
)
78+
79+
// Password Section
80+
Card(
81+
modifier = Modifier.fillMaxWidth(),
82+
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
83+
) {
84+
Column(
85+
modifier = Modifier.padding(16.dp),
86+
verticalArrangement = Arrangement.spacedBy(8.dp)
87+
) {
88+
Text(
89+
text = "Password",
90+
style = MaterialTheme.typography.titleMedium
91+
)
92+
OutlinedTextField(
93+
value = password,
94+
onValueChange = { password = it },
95+
label = { Text("Password") },
96+
visualTransformation = PasswordVisualTransformation(),
97+
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
98+
modifier = Modifier.fillMaxWidth()
99+
)
100+
}
101+
}
102+
103+
// Address Section
104+
Card(
105+
modifier = Modifier.fillMaxWidth(),
106+
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
107+
) {
108+
Column(
109+
modifier = Modifier.padding(16.dp),
110+
verticalArrangement = Arrangement.spacedBy(8.dp)
111+
) {
112+
Text(
113+
text = "Address Information",
114+
style = MaterialTheme.typography.titleMedium
115+
)
116+
OutlinedTextField(
117+
value = streetAddress,
118+
onValueChange = { streetAddress = it },
119+
label = { Text("Street Address") },
120+
modifier = Modifier.fillMaxWidth()
121+
)
122+
Row(
123+
modifier = Modifier.fillMaxWidth(),
124+
horizontalArrangement = Arrangement.spacedBy(8.dp)
125+
) {
126+
OutlinedTextField(
127+
value = city,
128+
onValueChange = { city = it },
129+
label = { Text("City") },
130+
modifier = Modifier.weight(1f)
131+
)
132+
OutlinedTextField(
133+
value = state,
134+
onValueChange = { state = it },
135+
label = { Text("State") },
136+
modifier = Modifier.weight(1f)
137+
)
138+
}
139+
OutlinedTextField(
140+
value = zipCode,
141+
onValueChange = { zipCode = it },
142+
label = { Text("ZIP Code") },
143+
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
144+
modifier = Modifier.fillMaxWidth()
145+
)
146+
}
147+
}
148+
149+
// Credit Card Section
150+
Card(
151+
modifier = Modifier.fillMaxWidth(),
152+
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
153+
) {
154+
Column(
155+
modifier = Modifier.padding(16.dp),
156+
verticalArrangement = Arrangement.spacedBy(8.dp)
157+
) {
158+
Text(
159+
text = "Credit Card Information",
160+
style = MaterialTheme.typography.titleMedium
161+
)
162+
OutlinedTextField(
163+
value = cardholderName,
164+
onValueChange = { cardholderName = it },
165+
label = { Text("Cardholder Name") },
166+
modifier = Modifier.fillMaxWidth()
167+
)
168+
OutlinedTextField(
169+
value = creditCardNumber,
170+
onValueChange = { creditCardNumber = it },
171+
label = { Text("Card Number") },
172+
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
173+
modifier = Modifier.fillMaxWidth()
174+
)
175+
Row(
176+
modifier = Modifier.fillMaxWidth(),
177+
horizontalArrangement = Arrangement.spacedBy(8.dp)
178+
) {
179+
OutlinedTextField(
180+
value = expiryDate,
181+
onValueChange = { expiryDate = it },
182+
label = { Text("MM/YY") },
183+
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
184+
modifier = Modifier.weight(1f)
185+
)
186+
OutlinedTextField(
187+
value = cvv,
188+
onValueChange = { cvv = it },
189+
label = { Text("CVV") },
190+
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
191+
visualTransformation = PasswordVisualTransformation(),
192+
modifier = Modifier.weight(1f)
193+
)
194+
}
195+
}
196+
}
197+
198+
// Submit Button
199+
Button(
200+
onClick = { /* Handle form submission */ },
201+
modifier = Modifier.fillMaxWidth()
202+
) {
203+
Text("Submit Information")
204+
}
205+
}
39206
}
40207

41208
@Preview(showBackground = true)
42209
@Composable
43-
fun GreetingPreview2() {
210+
fun UserInfoFormPreview() {
44211
AndroidObservabilityTheme {
45-
Greeting2("Android")
212+
UserInfoForm()
46213
}
47214
}

0 commit comments

Comments
 (0)