---
title: Session Replay
description: Session Replay captures a visual sequence of user interactions in your app by recording periodic screenshots.
url: https://coralogix.com/docs/user-guides/rum/sdk-installation/android/features/session-replay/
product: rum
feature: Android SDK
feature_id: mobile-sdk-android
feature_identifier: rum/mobile-sdk-android
last_updated: '2026-10-14'
---

# Session replay

Session Replay captures a visual sequence of user interactions in your app by recording periodic screenshots. This allows you to analyze what the user saw before, during, and after an issue — helping you quickly pinpoint the source of errors, performance slowdowns, or unexpected behavior. Use Session Replay to recreate the user experience and better understand the context behind each session.

## Prerequisites

Before enabling Session Replay, you must install and initialize the [Coralogix Android RUM SDK](https://coralogix.com/docs/user-guides/rum/sdk-installation/android.md#coralogix-android-rum-sdk) in your app.

Note

The Session Replay feature extends the Android RUM SDK and must be initialized after the base SDK setup. Attempting to initialize Session Replay without the base SDK will result in an initialization error.

## Session replay by default

Session Replay on Android:

* Evaluates UI one frame per second (FPS) and captures only if the layout changed since the previous screenshot.
* Masks all text inputs by default (controlled by `maskAllTexts`).
* Optionally masks only specific text values or regex patterns (via `textsToMask` when `maskAllTexts` is `false`).
* Redacts sensitive input field types (passwords, emails, numbers, etc.) via `maskInputFieldsOfTypes`.
* Optionally masks all image views when `maskAllImages` is enabled.
* Samples 100% of sessions by default.
* Sample frames at 1fps.

You can customize these defaults using the configuration options below.

## Configure and initialize

Use the `SessionReplayOptions` class to configure capture behavior, privacy masking, and sampling.

### Example

```
class MyApp : Application() {

    override fun onCreate() {

        super.onCreate()



        // step 1: initialize the Android RUM SDK

        val sdkOptions = CoralogixOptions(

           applicationName = "<YOUR_APP_NAME>",

           coralogixDomain = CoralogixDomain.EU1,

           publicKey = "<YOUR_PUBLIC_KEY>"

        )

 

        CoralogixRum.initialize(

           application = this,

           options = sdkOptions

        )



        // now you can safely initialize SessionReplay

        val sessionReplayOptions = SessionReplayOptions(

            captureScale = 0.5f, // Scale screenshots to half resolution

            captureCompressQuality = 0.8f, // Compress images for size optimization

            sessionRecordingSampleRate = 80, // Capture 80% of sessions

            autoStartSessionRecording = true, // Start recording automatically

            maskAllTexts = false, // Do not mask all text by default

            textsToMask = listOf("password", "secret.*"), // Mask specific words or regex patterns

            maskInputFieldsOfTypes = listOf(

                EditTextType.PASSWORD,

                EditTextType.EMAIL

            ),

            maskAllImages = true, // Mask all image views

        )

   

        SessionReplay.initialize(this, sessionReplayOptions)

    }

}
```

## Options reference

Configuration fields for `SessionReplayOptions`:

| Option                         | Type                 | Description                                                                                                                                                                                                                                                                             | Default       |
| ------------------------------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `autoStartSessionRecording`    | `Boolean`            | Start recording automatically                                                                                                                                                                                                                                                           | `true`        |
| `captureScale`                 | `Float`              | Screenshot resolution scale factor (0–1). Lower values reduce size and memory usage                                                                                                                                                                                                     | `0.5f`        |
| `captureCompressQuality`       | `Float`              | Image compression (0–1). Lower values reduce fidelity and file size                                                                                                                                                                                                                     | `1.0f`        |
| `sessionRecordingSampleRate`   | `Int` (0–100)        | % of sessions to capture                                                                                                                                                                                                                                                                | `100`         |
| `maskAllTexts`                 | `Boolean`            | Redact all on-screen text                                                                                                                                                                                                                                                               | `true`        |
| `textsToMask`                  | `List<String>`       | Redact only specific text values or regex patterns (used when `maskAllTexts` is `false`)                                                                                                                                                                                                | `emptyList()` |
| `maskInputFieldsOfTypes`       | `List<EditTextType>` | Redact only specific input fields (e.g., password, email, phone)                                                                                                                                                                                                                        | `emptyList()` |
| `maskAllImages`                | `Boolean`            | Mask all image views on screen                                                                                                                                                                                                                                                          | `false`       |
| ~~`sampleFrameRatePerSecond`~~ | `Int` *(deprecated)* | **Deprecated — do not change.** 1 fps is the only supported capture rate. Values above 1 degrade app performance and make mask-rect alignment unreliable, causing sensitive content to bleed through masks during scroll and animation. This field will be removed in a future release. | `1`           |

Session replay also follows `sessionSampleRate`: it captures nothing while the session is sampled out, and recording resumes on its own when a later session is sampled in. `SessionReplay.isRecording()` reports whether recording is turned on through `startSessionRecording()` and `stopSessionRecording()`, so it stays `true` while a sampled-out session pauses capture.

## Image capture triggers

Screenshots are captured during session replay in the following cases:

* User interacts with the screen (tap, scroll, or swipe).
* User navigates between activities or fragments.
* A periodic capture is triggered at 1 fps (the `sampleFrameRatePerSecond` option is deprecated and will be removed in a future release)
* Error or crash occurs.
* Manual screen capture event via [`SessionReplay.captureScreenshot()`](#manually-capture-screen).

Android Session Replay combines periodic sampling with **event-based captures** (click, error), so you still see key moments even if they occur between checks.

Note

Periodic sampling triggers a capture but only process that frame if a meaningful layout change has taken place to avoid noise and improve performance.

## Start and stop recording

If `autoStartSessionRecording` is `false`, you can manually control recording:

```
SessionReplay.startSessionRecording()

SessionReplay.stopSessionRecording()
```

## Manually capture screen

Use `captureScreenshot` to capture a specific moment in the session:

```
SessionReplay.captureScreenshot()
```

## Privacy and masking

Coralogix provides flexible masking options to help you meet privacy requirements:

* **Text masking:** Use `maskAllTexts` to redact all on-screen text, or `textsToMask` to selectively mask specific text values or regex patterns. Patterns use Kotlin/Java regex syntax (e.g. `(?i)button` for case-insensitive matching).
* **Input field masking:** Protect sensitive data such as passwords, emails, phone numbers, and other input fields by specifying their `EditTextType`.
* **Image masking:** Use `maskAllImages = true` to redact all `ImageView` instances on screen.
* **View masking:** You can programmatically mark a specific view as sensitive:

```
import com.coralogix.android.sdk.session_replay.SessionReplay.maskView



mySensitiveView.maskView()
```

### Reading a view's mask state

Two extensions report whether a `View` is masked, so wrappers and hybrid plugins can ask the SDK instead of reimplementing the rule. Both are available from version 2.22.0:

```
import com.coralogix.android.sdk.session_replay.isCoralogixMasked

import com.coralogix.android.sdk.session_replay.isInsideCoralogixMaskedSubtree



view.isCoralogixMasked()               // this view only

view.isInsideCoralogixMaskedSubtree()  // this view or any ancestor
```

Both are top-level, so no `SessionReplay` receiver is needed — unlike `maskView()`, which is a member extension and has to be imported as `SessionReplay.maskView`.

They report the **explicit mark only**, using the SDK's own predicate for it, so the mark cannot drift. That is narrower than the masking the replay ultimately applies: a view masked by a configured policy (`maskAllTexts`, `textsToMask`, `maskInputFieldsOfTypes`, `maskAllImages`) reports `false` from both accessors, as does a composable masked with `Modifier.coralogixMasked()`, which is not a `View`-level mark. `false` therefore does not mean the replay leaves the view visible.

**A wrapper that holds the tapped view should always supply `UserInteractionDetails.isMasked`, using `isInsideCoralogixMaskedSubtree()`.** Leaving that field null falls back to the SDK's own coordinate hit-test, which is best-effort and under-reports by design: it answers `false` when the payload carries no coordinates (React Native scroll and swipe carry none), when several hybrid roots disagree about the point, and when no view tree is available. A wrapper holding the view has the exact answer, so it should not fall back to a guess.

Use `isInsideCoralogixMaskedSubtree()` for that field rather than `isCoralogixMasked()`. The SDK's own resolution inherits masking from ancestors, so a child of a `maskView()`-ed container resolves to `true`; the self-only accessor overrides that with `false` and sends the inner text verbatim.

Both read live view tags and walk the live parent chain, so call them on the UI thread. Off it, a concurrent reparent or tag change can produce a stale answer — which matters most for `UserInteractionDetails.isMasked`, where the result drives redaction.

### What masking covers

Marking a view as sensitive applies to its whole subtree — every child is masked too, and a child cannot be opted back out. Masking a container is therefore enough to cover everything inside it, which is the recommended way to protect a composite element such as a PIN keypad whose keys are individual views.

A masked view, and anything inside it:

* is covered by a black overlay in session replays;
* absorbs tap markers — a tap landing anywhere inside it draws no marker in the recording, so the replay does not reveal which part of the masked area was touched;
* reports its inner text as `***` in user interaction events.

Everything else on the interaction event is reported as-is — the element's id, class, resolved element name, and the touch coordinates — and the event is still emitted. This matches the Coralogix web SDK: masking hides what an element says, not what it is or that it was used. The event also carries `interaction_context.is_masked_element` (always present, `true`/`false`) so you can enforce a stricter policy yourself in `beforeSend` — see below.

> **Upgrading from 2.19.4:** that release also redacted the id, class, and element name of a masked element. From 2.20.0 those fields are reported as-is again to match the web SDK; use `is_masked_element` in `beforeSend` if you need them redacted.

> **Shared identifiers for masked keypads:** an element's resource name can identify it as plainly as its label — a keypad laid out as `key_0`…`key_9` spells out the entered sequence through ids alone. If that matters to you, give the keys one shared id (or drop masked interactions in `beforeSend`).

### Enforcing a stricter policy with `is_masked_element`

`is_masked_element` is `true` when the interaction targeted masked content (via `maskView()`, the masked-view tag, or `Modifier.coralogixMasked()` — independent of whether session replay is recording). It is `false` in every case where the SDK cannot resolve masking — deliberately under-reporting rather than guessing:

* a hybrid (React Native / Flutter) interaction reported without coordinates, such as React Native scrolls and swipes;
* more than one hybrid root is on screen **and** the candidate readings disagree — the coordinates are root-relative and carry no root identity, so their owner is ambiguous (two windows are routinely attached for a moment during an activity transition; when all candidates agree, the agreed answer is used);
* no view tree was available when the interaction was processed.

For hybrid interactions the resolution is best-effort at processing time: it reads the screen as it is when the event is processed, so a navigation between the tap and its processing can make the flag wrong in either direction. A wrapper that needs an exact answer supplies `UserInteractionDetails.isMasked`, which is authoritative and skips coordinate resolution entirely.

Drop every masked interaction:

```
beforeSend = { event ->

    if (event.interactionContext?.isMaskedElement == true) null else event

}
```

Keep the event but blank the coordinates:

```
beforeSend = { event ->

    if (event.interactionContext?.isMaskedElement == true) {

        event.copy(

            interactionContext = event.interactionContext?.copy(attributes = buildJsonObject {})

        )

    } else {

        event

    }

}
```

> **Jetpack Compose:** All masking options above apply to Compose content as well. Use `Modifier.coralogixMasked()` for explicit per-composable masking. See the [Compose SDK README](https://coralogix.com/docs/user-guides/rum/sdk-installation/android/jetpack-compose.md#session-replay-masking) for Compose-specific limitations (image detection, password-only input masking, and the `invisibleToUser()` known limitation).

Tip

Always verify your masking rules in staging before going live, especially for sensitive inputs like login screens and payment forms.
