---
title: Experience Analytics extension - Android
description: Understand the benefits of upgrading to the CSQ SDK
lastUpdated: 12 June 2026
source_url:
  html: https://docs.contentsquare.com/en/csq-sdk-android/product-analytics/dxae-setup/
  md: https://docs.contentsquare.com/en/csq-sdk-android/product-analytics/dxae-setup/index.md
---

> Documentation index: https://docs.contentsquare.com/llms.txt
> Use this file to discover all available pages before exploring further.

Use this page to set up the Mobile Experience Analytics extension for Contentsquare Product Analytics.

This will supercharge the Product Analytics platform with:

* Powerful Session Replay
* Heatmaps
* Analysis capabilities of Mobile API Errors and Crashes

This guide assumes that you have already implemented the [CSQ SDK for Product Analytics](../).

## Enable the extension in the Product Analytics UI

1. In Product Analytics, navigate to **Account** > **Manage** > **Replays & Heatmaps**.

2. If this is your first time setting up Session Replay, select the checkbox to accept the terms and conditions.

3. Click **Add web or mobile app** to launch the setup wizard. There is one configuration per app.

4. Select **Mobile** as platform.

5. Give your app a name and choose a storage location (**US** or **EU**).

6. Set a sampling rate for how many sessions to record.

7. Configure replay settings:

   * **App ID**: enter the bundle ID of your app (for example, `com.companyname.appname`).
   * **Quality Settings**: choose the recording fidelity for Wi-Fi and Cellular connections. The default for both is Medium; keep Cellular equal to or lower than Wi-Fi.
   * **User Consent**: enable this if your app requires explicit opt-in before recording. See [Privacy docs](../privacy/#opt-in) for the opt-in API.

8. Click **Save and Continue** to finish the wizard.

## Add the Errors plugins

Add the Contentsquare Network Gradle plugin to the plugins block in both your root-level and app-level Gradle files:

* Kotlin

  **build.gradle.kts**

  ```kotlin
  plugins {
     id("com.contentsquare.error.analysis.network") version "1.8.0" apply false
  }
  ```

  **app/build.gradle.kts**

  ```kotlin
  plugins {
     id("com.contentsquare.error.analysis.network")
  }
  ```

* Groovy

  **build.gradle**

  ```groovy
  plugins {
     id "com.contentsquare.error.analysis.network" version "1.8.0" apply false
  }
  ```

  **app/build.gradle**

  ```groovy
  plugins {
     id "com.contentsquare.error.analysis.network"
  }
  ```

Once this is done, the SDK running on your app will immediately start collecting the required data to provide Session Replay and Errors / Crashes (if enabled) into the Product Analytics platform.

## Enable error and crash collection

Error and crash collection is managed separately from the setup wizard. Navigate to **Account** > **Manage** > **Replays & Heatmaps**, click **Edit** next to your app configuration, then scroll to the relevant section under **General Settings**:

* **Error Capture**: toggles for **API Errors** and **Custom Errors**.
* **Crashes Capture**: toggle for **Crashes**.

Click **Save** when finished.

## Session Replay masking

The Session Replay feature replays every interaction of your users with your app. To respect the user's right to privacy, the Contentsquare SDK:

* Masks everything by default
* Allows you to control which part of the user interface is collected via our [Masking rules](#masking-rules)

Warning

If you decide to unmask elements leading to the collection of new data or use already collected data but for a different purpose, it is up to you to update your Google Play Data Safety declaration accordingly. Here are some common examples of data collected via unmasked Session Replay: Search history, Purchase history, Advertising data, and so on. See [Types of data and purpose](../privacy/).

Contentsquare is not intended nor designed to collect sensitive personal data (such as health, financial, or racial data). It is the customer's responsibility not to send any sensitive personal data to Contentsquare.

### Masking mechanisms

Every single UI element is converted into a highly pixelated image to reach a very low resolution. Text and images will appear very blurry so that the content cannot be identified.

#### Original vs Replay fully masked

![](https://docs.contentsquare.com/_astro/android-sr-masked.Bv63pOqr_Z1aMjBU.webp)

### Masking rules

Masking rules can be applied in two ways:

1. **Through remote masking configuration in the CSQ Console (for admin users):** These configurations are managed directly in the Console and take effect for all sessions as soon as the app is restarted or brought to the foreground See [How to customize masking rules from the Data Masking tab ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) in the Help Center.
2. **Using public masking APIs in the SDK (for mobile application developers):** These configurations require developer implementation and will be applied only after the mobile app has gone through its release cycle.

Masking rules set via the CSQ Console or the public APIs are applied according to different priorities, as described below. The SDK determines whether a view is masked by evaluating the rules in the order specified below. Once a rule is triggered, the state is set, and subsequent rules are not applied:

#### General case

| Rule | Configured via |
| - | - |
| 1. The app or SDK version is fully masked | [Data Masking tab in the CSQ Console ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 2. Remote Text or Image masking is defined | [Data Masking tab in the CSQ Console ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 3. An [instance](#maskingun-masking-by-instance) is specifically masked or unmasked | API |
| 4. A [type](#maskingun-masking-by-type) is masked or unmasked | API |
| 5. A [parent](#masking-and-unmasking-behaviors-on-a-parent-view) is specifically masked or unmasked | API |
| 6. A parent [type](#maskingun-masking-by-type) is masked or unmasked | API |
| 7. Remote Text or Image unmasking is defined | [Data Masking tab in the CSQ Console ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 8. Otherwise the [default masking state](#default-masking) is applied | API |

#### Exception: Text input fields

Special consideration is needed for text inputs, as the risk of leaking personal data is higher with these elements. As a result, the SDK applies slightly different rules to `EditText` and Jetpack Compose `TextField`:

| Rule | Configured via |
| - | - |
| 1. The app or SDK version is fully masked | [Data Masking tab in the CSQ Console ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 2. Remote Text input masking is defined | [Data Masking tab in the CSQ Console ↗](https://support.contentsquare.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 3. An `EditText` [instance](#maskingun-masking-by-instance) is specifically unmasked | API |
| 4. The `EditText` [type](#maskingun-masking-by-type) is unmasked | API |
| 5. Otherwise a text input field remains masked | default |

Specifically, default masking and parent masking won't change the masking state of text inputs.

### Public masking APIs

#### Default masking

Warning

You should not perform any call to Contentsquare APIs before the first visible Activity to be sure the SDK is initialized. If you perform a call too soon, it will be discarded and you will see the following log:

```plaintext
CSLIB Contentsquare SDK: Unable to call the public API, make sure you are not opted out of the Contentsquare tracker and SDK was correctly initialized.
```

All Android View elements, their subclasses, and Jetpack Compose components are fully masked by default.

Change the default masking state with:

```kotlin
/**
* Sets the default masking state for the Contentsquare's Session Replay features.
*
* @param masked A boolean value indicating whether masking should be enabled (true) or disabled (false).
*/
@JvmStatic
fun setDefaultMasking(masked: Boolean)
```

Note

Setting the default masking state to `false` does not affect `EditText` or `TextField` (Jetpack Compose).

#### Masking/Un-masking by instance

Warning

Animations are not supported when masking by instance.

* Views

  Use `mask(view: View)` and `unmask(view: View)` methods to mask or unmask a specific view instance.

  ```kotlin
  /**
  * Masks the contents of a specific view.
  *
  * @param view an instance of [View] to mask.
  */
  @JvmStatic
  fun mask(view: View)
  ```

  ```kotlin
  /**
  * Unmasks the contents of a specific view.
  *
  * @param view an instance of [View] to unmask.
  */
  @JvmStatic
  fun unmask(view: View)
  ```

  Alternatively, you can also apply the masking rule to a specific view instance directly with `View.csqMaskContents(enable: Boolean)` extension function.

  ```kotlin
  /**
   * [View] extension function to mask the contents of a view.
   * This extension function can be used as alternative to [CSQ.mask] and [CSQ.unmask].
   *
   * @param enable A boolean value indicating whether masking should be enabled (true) or disabled (false) for this view.
   */
  fun View.csqMaskContents(enable: Boolean)
  ```

  Following the previous example, we have the views hierarchy in which all instances of TextView type are unmasked with the default masking state of all the views are masked by default.

  ![](https://docs.contentsquare.com/_astro/android-unmask-view-instance.BDxkztL1_Kt4fa.webp)

  In this example, if you want to mask a specific text view TextView1, call `mask(textView1)`. The view instance rule overrides the type rule and the default rule.

* Jetpack Compose

  Use the `CsqMask(enable: Boolean, block: @Composable () -> Unit)` composable function to mask or unmask a composable.

  ```kotlin
  /**
   * A composable function that applies masking to Compose UI content.
   *
   * @param enable A boolean value indicating whether masking should be enabled (true) or disabled (false).
   * @param block A composable lambda function representing the content to apply masking condition.
   */
  @Composable
  fun CsqMask(enable: Boolean, block: @Composable () -> Unit)
  ```

  For example:

  ```kotlin
  CsqMask(true) {
     Text(
        text = "Text"
     )
  }
  ```

#### Masking/Un-masking by type

Although the SDK allows masking elements by type for convenience but it isn't the recommended masking mechanism because it impacts all screens of your application.

Use this when you have a specific class for presenting user information or for displaying the user profile picture to make sure it always stays masked, for instance.

Warning

Animations are not supported when masking by type.

* Views

  Use `mask(type: Class<*>)` and `unmask(type: Class<*>)` methods to mask or unmask a specific type.

  ```kotlin
  /**
  * Masks the contents of the view for all instances of a specific [Class] and subclass of a [View].
  * This will be applied only for Session Replay feature.
  *
  * @param type a [Class] object representing the type to be masked.
  */
  @JvmStatic
  fun mask(type: Class<*>)
  ```

  ```kotlin
  /**
  * Unmasks the contents of the view for all instances of a specific [Class] and subclass of a [View] for
  * Session Replay feature.
  *
  * @param type a [Class] object representing the type to be unmasked.
  */
  @JvmStatic
  fun unmask(type: Class<*>)
  ```

  For example, given the views hierarchy:

  ![](https://docs.contentsquare.com/_astro/android-unmask-type.C_f9t2FE_Z1WTWsj.webp)

  The default masking state of all the views is masked and, for example, to unmask all **TextView** types, you can call `unmask(TextView.class)` then all instances of **TextView** type will be unmasked.

  In this example, the button is also impacted by the `unmask()` call because the button is also a **TextView** type (a subclass of **TextView**). The **EditText** is also a **TextView** type but isn't impacted by `unmask(TextView.class)` call because the **EditText** type is [masked by default](#masking-rules).

* Jetpack Compose

  It is not possible to mask or unmask composables by type.

  Alternatively, you can mask or unmask a group of composables by wrapping multiple composables within the same parent, then applying the `CsqMask(enable: Boolean, block: @Composable () -> Unit)` on the parent.

#### Masking and Unmasking behaviors on a parent view

* Views

  You can mask ViewGroups (and subclasses such as RelativeLayout, LinearLayout, ConstraintLayout, and so on). When a parent view is masked all of its children inherit the masking state. The children's view masking state can be overridden by using a specific API to mask or unmask by instance or by type.

  ![](https://docs.contentsquare.com/_astro/android-unmask-parent.LsPThKi1_Z1Ixi8p.webp)

  For example, given the view hierarchy with the default masking state. All the views are masked by default in the initial state and in this case, you may want to unmask all children's views of the parent view **LinearLayout1**, you can call `unmask(linearLayout1)` then all children views of the parent view **LinearLayout1** will be unmasked. As noticed here the **EditText** is not impacted from the parent call because the **EditText** type is [masked by default](#masking-rules).

* Jetpack Compose

  You can mask a parent layout (such as `Column`, `Row`, or `Box`). If a parent composable is masked, then all of its children inherit the masking state from the parent. The children's masking state can be overridden by using the dedicated `CsqMask(enable: Boolean, block: @Composable () -> Unit)` function:

  ```kotlin
  CsqMask(true) {
     Column {
        Row {
           CsqMask(false) {
              // unmask this `Text` composable
              Text(
                 text = "Text",
              )
           }
        }
        Row {
           Text(
              // this `Text` composable will be masked, as it inherits the masking state from the parent
              text = "Text 2",
           )
        }
     }
  }
  ```

#### Masking and Unmasking of Bottom Sheets, Dialogs, and Menus

Contentsquare Android SDK 4.39.0 and earlier

If you were using Contentsquare Android SDK versions before 4.39.0 in your mobile app, then masking is automatically enforced on all bottom sheets, dialogs, and menus, as these elements were not supported in earlier versions. To unmask any of these elements, contact your CSM to disable the forced masking and use the APIs described below.

**Bottom Sheets**

Bottom sheets can be masked or unmasked using the same APIs available for any other view, whether you're working with Android Views or Jetpack Compose. To apply the masking behavior, ensure that you call the appropriate API after the bottom sheet has been created.

**AlertDialogs**

* Views

  Dialogs, such as AlertDialog, TimePickerDialog, and DatePickerDialog, can be masked or unmasked using the APIs provided below. In addition, the masking behavior of individual components within these dialogs can be customized using the provided Kotlin extension functions.

  For instance, if an AlertDialog is fully masked by default, you can selectively unmask specific elements—such as the title or action buttons—using the extension functions provided below for more granular control.

  For example, given an AlertDialog with the default masking state, all views in the dialog are masked by default in the initial state. In this case, you may want to unmask the title and buttons of the dialog. To do that, we provide Kotlin extension functions to the Dialog API to make it easy.

  ```kotlin
  /**
   * Masks the entire [AlertDialog].
   */
  fun AlertDialog.csqMask()


  /**
   * Masks the title of the [AlertDialog].
   */
  fun AlertDialog.csqMaskTitle()


  /**
   * Masks the message of the [AlertDialog].
   */
  fun AlertDialog.csqMaskMessage()


  /**
   * Masks the positive button of the [AlertDialog].
   */
  fun AlertDialog.csqMaskPositiveButton()


  /**
   * Masks the negative button of the [AlertDialog].
   */
  fun AlertDialog.csqMaskNegativeButton()


  /**
   * Unmasks the entire [AlertDialog].
   */
  fun AlertDialog.csqUnMask()


  /**
   * Unmasks the title of the [AlertDialog].
   */
  fun AlertDialog.csqUnMaskTitle()


  /**
   * Unmasks the message of the [AlertDialog].
   */
  fun AlertDialog.csqUnMaskMessage()


  /**
   * Unmasks the positive button of the [AlertDialog].
   */
  fun AlertDialog.csqUnMaskPositiveButton()


  /**
   * Unmasks the negative button of the [AlertDialog].
   */
  fun AlertDialog.csqUnMaskNegativeButton()
  ```

  Note

  The mask API for dialogs should be called immediately after the `show` method in the main thread.

  Example:

  ```kotlin
  val builder: AlertDialog.Builder = AlertDialog.Builder(context)
  builder.setMessage("This is a dialog. This text should be masked")
  builder.setTitle("Dialog Title")
  builder.setCancelable(true)
  builder.setPositiveButton("Close") { ... }
  builder.setNegativeButton("Idle") { ... }
  val alertDialog: AlertDialog = builder.create()
  alertDialog.show()
  alertDialog.csqUnMaskTitle()
  alertDialog.csqUnMaskPositiveButton()
  alertDialog.csqUnMaskNegativeButton()
  ```

* Jetpack Compose

  The AlertDialog composable in Jetpack Compose can be masked or unmasked using the same APIs applied to other composables. Simply use the appropriate `CsqMask` composables function on the elements you want to mask or unmask within the dialog.

  Example:

  ```kotlin
  AlertDialog(
     icon = {
        Icon(icon, contentDescription = "Example Icon")
     },
     title = {
        Text(text = dialogTitle)
     },
     text = {
        // Mask the dialog text
        CsqMask(true) {
           Text(
              text = dialogText
           )
        }
     },
     onDismissRequest = {
        onDismissRequest()
     },
     confirmButton = {
        TextButton(
           onClick = {
              onConfirmation()
           }
        ) {
           // Unmask the dialog confirm button
           CsqMask(false) {
              Text(
                 "Confirm"
              )
           }
        }
     }
  )
  ```

**TimePickerDialog and DatePickerDialog**

* Views

  Time and date pickers function similarly to AlertDialogs but contain their own distinct UI components. These sections can be selectively masked or unmasked using the APIs provided below.

  To support masking on this dialogs we added the following APIs:

  ```kotlin
  /**
   * Masks the entire [DatePickerDialog].
   */
  fun DatePickerDialog.csqMask()


  /**
   * Masks the header of the DatePicker, which typically displays the selected date.
   * This affects the [DatePickerDialog].
   */
  fun DatePickerDialog.csqMaskHeader()


  /**
   * Masks the calendar view (day picker) of the [DatePickerDialog].
   */
  fun DatePickerDialog.csqMaskCalendar()


  /**
   * Masks the button panel of the [DatePickerDialog], typically containing "OK" and "Cancel" buttons.
   */
  fun DatePickerDialog.csqMaskButtonPanel()


  /**
   * Unmasks the entire [DatePickerDialog].
   */
  fun DatePickerDialog.csqUnMask()


  /**
   * Unmasks the header of the DatePicker.
   * This affects the [DatePickerDialog].
   */
  fun DatePickerDialog.csqUnMaskHeader()


  /**
   * Unmasks the calendar view (day picker) of the DatePicker.
   * This affects the [DatePickerDialog].
   */
  fun DatePickerDialog.csqUnMaskCalendar()


  /**
   * Unmasks the button panel of the [DatePickerDialog].
   */
  fun DatePickerDialog.csqUnMaskButtonPanel()


  /**
   * Masks the entire TimePicker view within the [TimePickerDialog].
   */
  fun TimePickerDialog.csqMask()


  /**
   * Masks the header of the TimePicker, including the time display and input mode header.
   * This affects the [TimePickerDialog].
   */
  fun TimePickerDialog.csqMaskHeader()


  /**
   * Masks the radial picker (clock face) of the TimePicker.
   * This affects the [TimePickerDialog].
   */
  fun TimePickerDialog.csqMaskRadialPicker()


  /**
   * Masks the input mode view of the TimePicker (keyboard input for time).
   * This affects the [TimePickerDialog].
   */
  fun TimePickerDialog.csqMaskInputMode()


  /**
   * Masks the button panel of the [TimePickerDialog], including positive, negative, and toggle mode buttons.
   */
  fun TimePickerDialog.csqMaskButtonPanel()


  /**
   * Unmasks the entire TimePicker view within the [TimePickerDialog].
   */
  fun TimePickerDialog.csqUnMask()


  /**
   * Unmasks the header of the TimePicker, including the time display and input mode header.
   * This affects the [TimePickerDialog].
   */
  fun TimePickerDialog.csqUnMaskHeader()


  /**
   * Unmasks the radial picker (clock face) of the TimePicker.
   * This affects the [TimePickerDialog].
   */
  fun TimePickerDialog.csqUnMaskRadialPicker()


  /**
   * Unmasks the input mode view of the TimePicker (keyboard input for time).
   * This affects the [TimePickerDialog].
   */
  fun TimePickerDialog.csqUnMaskInputMode()


  /**
   * Unmasks the button panel of the [TimePickerDialog], including positive, negative, and toggle mode buttons.
   */
  fun TimePickerDialog.csqUnMaskButtonPanel()
  ```

* Jetpack Compose

  The Time and Picker composables in Jetpack Compose can be masked or unmasked using the same APIs applied to other composables. Simply use the appropriate `CsqMask` composables function on the elements you want to mask or unmask within the dialog.

**Custom Dialogs**

* Views

  If you're creating a custom dialog by extending the Dialog base class and inflating a layout using the setContentView method, you can mask or unmask individual views within your custom view hierarchy, using the masking APIs.

* Jetpack Compose

  The custom dialogs in Jetpack Compose can be masked or unmasked using the same APIs applied to other composables. Simply use the appropriate `CsqMask` composables function on the elements you want to mask or unmask within the dialog.

**Menu Items**

* Views

  Menu items captured by Session Replay can be selectively masked or unmasked.

  For example, consider a context menu, including an action item and another item displaying the user's name (see XML example below). If masking is disabled by default, you may want to mask sensitive menu items to prevent personal data from appearing in Session Replays. Conversely, if masking is enabled by default, you may choose to unmask certain non-sensitive items to keep them visible.

  To achieve this, simply apply masking or unmasking to individual menu items as needed.

  ```xml
  <?xml version="1.0" encoding="utf-8"?>
  <menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
      <item
          android:id="@+id/action_settings"
          android:orderInCategory="10"
          android:title="@string/action_settings"
          app:showAsAction="never" />
      <item
          android:id="@+id/profile_settings"
          android:orderInCategory="100"
          android:title="Name Placeholder"
          app:showAsAction="never" />
  </menu>
  ```

  Note

  This API does not need to be called strictly after menu inflation; it can be invoked earlier if necessary. As a best practice, apply it during or after menu inflation when you have clear context about what is being masked and where.

  ```kotlin
  override fun onCreateOptionsMenu(menu: Menu): Boolean {
     // Inflate the menu; this adds items to the action bar if it is present.
     menuInflater.inflate(R.menu.activity_main, menu)
    val item = menu.findItem(R.id.profile_settings)
    item.title = current_user.fullName


    // Unmask action menu item
     CSQ.unmaskMenuItem(R.id.action_settings)
    // Mask profile menu item that contains user's name
     CSQ.maskMenuItem(R.id.profile_settings)
     return true
  }
  ```

* Jetpack Compose

  The Menu components in Jetpack Compose can be masked or unmasked using the same APIs applied to other composables.

  Use the appropriate `CsqMask` composable function on the elements you want to mask or unmask within the menu.

#### Handling Transparent Views

The SDK processes [masked views first](#masking-rules), ensuring that if a view has transparency and is not masked, it will not expose any personal data since masked views have already been processed.

This approach ensures the protection of sensitive information, even in UIs with transparent views.

Warning

If the view below the transparent view is a **WebView**, and you unmask the view on top of it (for example a dialog progress bar), it leaks the content of the WebView. This is a known limitation.

On the left, a possible implementation of a payment screen in a WebView. On the right, the result in Session Replay.

![](https://docs.contentsquare.com/_astro/transparent-view-on-webview-combined-no-bg.DXjZ0GVu_1MC2wU.webp)

### Implementation recommendations

#### Where to perform masking operations

Masking operation should be performed before the first draw of the view and it should always be called on the UI thread.

At the **Activity** level, call the masking API methods on the `onCreate` lifecycle method.

At the **Fragment** level, call the masking API methods on the `onViewCreated` lifecycle method.

Note

The masking API stores the view element to be masked or unmasked inside a WeakHashMap, repeating the masking operation multiple times on the same element will override the old value that is already stored in the map.

#### Keeping track of what is masked

The SDK doesn't provide a list of what is currently masked, if you need to keep track, you probably will have to write your specific wrapper.

#### Masking operations performance impact

You can call `unmask(View view)` multiple times without impacting the SDK performance or your app.

### Masking limitations

#### Maps

You can mask or unmask a Map, that will then be captured as an image in the replay. However, you won't be able to mask or unmask specific elements within the Map.

#### Navigate between fragments using animations

Masking rules don't apply on Fragments using animations. So to mask this type of Fragment, mask the container view where the Fragment is inserted, rather than masking its content. This ensures that the Fragment content remains masked during transitions.

Additionally, you can unmask some specific views within the Fragment content, by using `CSQ.unmask(view: View)`.

#### Rotated or Scaled Views

The masking solution relies on the bounding box reported by a view to determine the area that should be masked. In most cases, this approach is sufficient, as the bounding box accurately reflects the view's position and dimensions on the screen.

However, when a view is rotated or scaled, the bounding box no longer represents the view's actual position or size after the transformation.

To prevent any personal data from being leaked, we advise adopting one of the following approaches:

* **Mask the Parent View**: Apply masking to the parent container of the transformed view. This ensures that the entire area encompassing the rotated or scaled view is covered, regardless of transformations.

* **Mask the Entire Window**: In case of complex layouts, transformations, or critical scenarios where absolute certainty is required, mask the entire window to guarantee that no personal data is exposed.

These approaches provide the highest level of confidence in preventing personal data leakage while working within the constraints of the current implementation.
