Android Masking Best Practices

In Glance Mobile Cobrowse, masking is a critical feature used to protect sensitive or private information on the end user’s screen during a live session with an agent. By selectively hiding specific UI elements—such as passwords, credit card fields, or health data—from the representative’s view, masking allows companies to safeguard sensitive information while still providing real-time support.

To enable masking during sessions, developers should use the Glance Mobile SDK to designate which portions of the screen should be hidden from agents. Masking integrates with the native rendering layer of the mobile app, allowing developers to selectively hide sensitive views. There are four main contexts in which masking can be applied:

  • View Masking (Traditional Android Views)
  • Jetpack Compose Masking
  • WebView Masking
  • Keyboard Masking

Below are best practices for each of these contexts, as well as general masking guidelines that apply in all situations.


General Masking

In all contexts, once an element is designated to be masked by the SDK, it will remain obscured from the agent’s view for the duration of all current and future sessions. Given this behavior, masking should be applied at the time views are created—during layout or initialization—rather than waiting until a session has begun. This ensures that masking is consistently applied, even if a session starts after the views are already rendered or if views are recreated during the app lifecycle.

Similarly, when a view is destroyed, masking should be removed. In most cases, masking is automatically removed when the view is destroyed, but best practice is to explicitly remove it in the view’s destructor. Masking is always applied if the view is visible. If a view is destroyed and no longer present, masking will be removed and will not appear during sessions.

There are, however, situations in which a mask might be applied even though the view is not visible—primarily when the view is covered by another view, which the SDK may not detect. In such cases, if masking is not desired, it should be removed and reapplied when the view becomes visible again.

Takeaways

  • Masked elements remain hidden for the entire session once registered with the SDK.
  • Apply masking during view initialization or layout, not after a session begins.
  • Always remove masking when a view is destroyed or removed and no longer used.

View Masking

For apps built using traditional Android Views, masking is performed by passing view IDs or view references to the SDK. Masking can be removed by making calls to remove masked views using references to the masked views. Below are the methods that should be used for masking Android views:

// Adding Masked Views by ID
Glance.addMaskedViewId(R.id.sensitiveTextField, "Masked text field");
Glance.addMaskedViewId(R.id.sensitiveLabel, "Masked label");
Glance.addMaskedViewId(R.id.sensitiveButton, "Masked button");
// Adding Masked Views by reference
Glance.addMaskedView(textField, "Masked text field");
Glance.addMaskedView(label, "Masked label");
Glance.addMaskedView(button, "Masked button");
// Removing Masked Views
Glance.removeMaskedViewId(R.id.sensitiveTextField);
Glance.removeMaskedView(textField);

When using traditional Android Views, the SDK utilizes the view’s visibility to determine when masks should be displayed. In these cases, you typically do not need to manually add or remove masked views based on visibility. However, there are scenarios where masks might remain visible even when the corresponding views are obstructed—for example, when covered by a Dialog or a Compose view rendered in a way that prevents the SDK from detecting the obstruction.

In such cases, you may need to explicitly call removeMaskedView when the view is no longer visible and reapply it using addMaskedView once it becomes visible again.

Note: This is a rare situation. In most cases, manual intervention is not required.


Jetpack Compose Masking

Since Jetpack Compose does not expose elements via referenceable IDs and manages its UI through an internal layer, masking is applied using modifiers. Because of this internal architecture, the SDK has limited ability to detect whether a Compose view is obstructed by other UI elements. As a result, masking will remain active for any Compose element that is present on the screen, even if it’s visually covered by another element.

In these cases, the app is responsible for managing the view’s visibility and conditionally applying the masking modifier only when the view is actually visible.

Below is an example that illustrates how to conditionally apply a masking modifier to a Compose view:

@Composable
fun MaskedPasswordField() {
    var isVisible by remember { mutableStateOf(true) }
    if (isVisible) {
        OutlinedTextField(
            value = "",
            onValueChange = {},
            label = { Text("Enter Password") },
            modifier = Modifier.glanceMask("Password Field")
        )
    } else {
        OutlinedTextField(
            value = "",
            onValueChange = {},
            label = { Text("Enter Password") }
        )
    }
}

Jetpack Compose re-renders UI elements in response to state changes, so unnecessary or frequent updates to state values can lead to excessive recompositions and performance overhead. For optimal performance, only update state variables when the visibility or content of a view meaningfully changes, rather than on every minor event or frame. Efficient and minimal recomposition is essential to maintaining performance in Jetpack Compose applications.


WebView Masking

Masking in a WebView is applied using CSS selectors or element IDs. The mask will be displayed as long as the WebView is visible and present in the view hierarchy. Setting the WebView’s visibility to GONE will also hide its masks. However, removing the WebView from the view hierarchy does not automatically remove masking.

WebView masking differs slightly from native view masking. It requires that you explicitly remove the GlanceWebViewJavascriptInterface when masking is no longer needed. While the operating system typically cleans up a WebView and its associated JavaScript interface when it is no longer in use, there are cases where the OS may retain a WebView for performance reasons. To ensure proper cleanup, it’s best practice to manually remove masking from the WebView when it’s no longer required.

Applying WebView masking:

String queryMaskSelectors = ".mask_1, .mask_2, #mask_3, .mask_4, #hplogo";
String labelsToMask = "mask 1, mask 2, mask 3, mask 4, LOGO";
WebView webView = new WebView(context);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setDomStorageEnabled(true);
GlanceWebViewJavascriptInterface jsInterface = new GlanceWebViewJavascriptInterface(webView);
webView.addJavascriptInterface(jsInterface, "GLANCE_Mask");
GlanceWebViewClient webViewClient = new GlanceWebViewClient(queryMaskSelectors, labelsToMask);
webView.setWebViewClient(webViewClient);

Cleanup when done:

webView.removeJavascriptInterface("GLANCE_Mask");
jsInterface.onDestroy();

Keyboard Masking (Optional)

If your app includes custom keyboards or input accessory views that display sensitive content, you should apply the same masking techniques used for regular Android views. This can be done using either addMaskedView for traditional views or the glanceMask modifier for Jetpack Compose, depending on your implementation.

For standard system keyboards, masking can be applied using the following method:

Glance.maskKeyboard(true);

This can be set during session initialization:

DefaultSessionUI.init(
    activity,
    startParams,
    true, // maskKeyboard parameter
    groupId,
    visitorId,
    eventsListener
);