Record Android sessions

Use the sessionReplay module in Android applications.

Prerequisites

Introduction

This page explains how to use the sessionReplay module in Android applications. This module provides several APIs so that you can choose which ones work best for your application.

Supported languagesJava, Kotlin
compileVersion34
minVersion21
targetVersion31
Java version1.8
Kotlin version1.7.20
APIs
Most APIs are methods within SplunkRum.instance.sessionReplay:

In addition, there are some Kotlin-specific extensions (properties) of standard Android classes, such as:

SplunkRum.instance.sessionReplay

Get a sessionReplay instance, which is used to configure and start the recording.

The instance doesn't automatically start recording when you create it. To start recording, call its start() method.

Kotlin
val sessionReplay = SplunkRum.instance.sessionReplay
Java
SessionReplay sessionReplay = SessionReplay.getInstance();

Start recording

Kotlin
SplunkRum.instance.sessionReplay.start()
Java
SessionReplay.getInstance().start();

Stop recording

There is no need to manually stop the recording when the application enters the background; the recording process automatically stops, and the sessionReplay module provides the final data chunk. When the application returns to the foreground, the sessionReplay module resumes recording automatically, provided it was active before the application was previously suspended.

If the application is forcibly terminated (not just suspended to the background) or crashes, the behavior of the sessionReplay module depends upon the platform:

  • If the system permits some time to process the incident, the last data chunk is immediately made available.

  • If not, the most recent replay data chunk prior to the incident is reconstructed and published when the sessionReplay module is re-initialized during the application's next run. This includes the corresponding metadata (the actual start and end time stamps of the reconstructed chunk).

After the incident, the sessionReplay module doesn't resume recording automatically.

Kotlin
SplunkRum.instance.sessionReplay.stop()
Java
SessionReplay.getInstance().stop();

Get preferences

Kotlin
SplunkRum.instance.sessionReplay.preferences
Java
SessionReplay.getInstance().getPreferences();

Set the rendering mode

When your application displays sensitive or unnecessary data that you don’t want to record, you can set a rendering mode to protect or hide that information. Valid values:

  • NATIVE: Regularly captures the application screen which the sessionReplay module immediately processes to remove sensitive data. The frames are then complied to make the session recording. The representation of the recording is video.
    Note: If RenderingMode is NATIVE, both the video and wireframe recordings operate simultaneously due to the nature of the implementation. Consequently, both modes are accessible to the player.
  • WIREFRAME_ONLY: Renders the application using only a wireframe representation of the screen data. No user data is recorded. The representation of the recording is in JSON format.
Kotlin
enum class RenderingMode {
    NATIVE,
    WIREFRAME_ONLY
}
val preferences = SplunkRum.instance.sessionReplay.preferences

val renderingMode: RenderingMode = preferences.renderingMode
preferences.renderingMode = RenderingMode.NATIVE
Java
public enum RenderingMode {
    NATIVE,
    WIREFRAME_ONLY
}
Preferences preferences = SessionReplay.getInstance().getPreferences();

RenderingMode renderingMode = preferences.getRenderingMode();
preferences.setRenderingMode(RenderingMode.NATIVE);

Set the recording mask

In cases where areas of the app shouldn't be recorded, but cannot be defined by a View, you can use the RecordingMask object:

Kotlin
val recordingMask = RecordingMask(
  listOf(
    RecordingMask.Element(
      Rect(left, top, right, bottom),
      RecordingMask.Element.Type.COVERING|RecordingMask.Element.Type.ERASING
    )
  )
)

SplunkRum.instance.sessionReplay.recordingMask = recordingMask
Java
ArrayList<RecordingMask.Element> elements = new ArrayList<>();

elements.add(
  new RecordingMask.Element(
  	new Rect(left, top, right, bottom),
  	RecordingMask.Element.Type.COVERING|RecordingMask.Element.Type.ERASING
  )
);

RecordingMask recordingMask = new RecordingMask(elements);

SessionReplay.getInstance().setRecordingMask(recordingMask);

You can only have one RecordingMask set at a time, but it can contain a list of RecordingMask.Element objects to cover multiple areas at once.

The RecordingMask.Element can be one of two types:

  • RecordingMask.Element.Type.COVERING: The area defined by the element Rect is not recorded.

  • RecordingMask.Element.Type.ERASING : The area defined by the element Rect is recorded even if a previous RecordingMask.Element inside a list was covering the area.

The following screenshots describe a RecordingMask in action.

On the left:

  • The blue box represents a video_item element.

  • The red box represents a video_item_image element.

On the right:

  • The video_item element (blue box) has a .COVERING value. The .COVERING value masks the element in the session recording.

  • The video_item_image element (red box) has an .ERASING value. The image is visible in the session recording because the .ERASING value cancels the .COVERING value.

Set the sensitivity of a UI element or view

When you mark UI elements or views as sensitive, the sessionReplay module masks them in the recording directly on the device. The way to mark elements as sensitive depends on the element type:

  • Jetpack Compose layout elements can be marked as sensitive using the isSensitivite modifier:

    Text(
      text = "I'm sensitive text",
      modifier = Modifier
        .sessionReplay(
          isSensitive = true
        )
    )
  • XML layouts: View

    You can set the sensitivity on any View instance:

    Kotlin
    sampleView.isSensitive = true|false|null
    Java
    SessionReplay.getInstance().getSensitivity().setViewInstanceSensitivity(sampleView, true|false|null);

    You can also tag any View directly in the XML layout file:

    <View>
        <tag android:id="@id/sr_sensitivity" android:value="true|false"/>
    </View>
  • XML layouts: Class

    You can set the sensitivity on all instances of a Class that extends a View rather than setting the sensitivity on a specific View:

    Kotlin
    SampleViewClass::class.isSensitive = true|false|null
    Java
    SessionReplay.getInstance().getSensitivity().setViewInstanceSensitivity(view, true|false|null);
  • Default sensitive classes

    By default, the EditText class is set to sensitive. To override the sensitivity at the class or instance level, instance sensitivity needs to be set to false or null.

  • Sensitivity prioritization

    When determining if the View instance is sensitive, the resolution process checks the sensitivity in a strict order.

    View instances are not recorded if:

    1. The XML has the sl_sensitivity tag set to true.

    2. The sensitivity is set to true.

    3. the Class sensitivity is set to true.

  • Class hierarchy and sensitivity

    Sensitivity set to a more specific class (deeper in the inheritance tree) has higher priority. Let's demonstrate this principle in the example using the inheritance tree:

    If TextView is set to be sensitive and RadioButton is explicitly set to not be sensitive:

    Kotlin
    TextView::class.isSensitive = true
    RadioButton::class.isSensitive = false
    Java
    SessionReplay.getInstance().getSensitivity().setViewClassSensitivity(TextView.class, true);
    SessionReplay.getInstance().getSensitivity().setViewClassSensitivity(RadioButton.class, false);

    These statements are factual if we assume no View instance-specific sensitivity is set:

    • All instances of TextView, Button, CompoundButton, RadioButton, Switch, and ToggleButton are sensitive.

    • All instances of RadioButton are not sensitive, even though RadioButton inherits from the sensitive class TextView.

  • WebView layouts

    WebView isn't sensitive by default, but if you need to handle sensitivity within a WebView class, you can mark all sensitive elements on the displayed website as sensitive so that they are hidden. Make these HTML elements part of the CSS .session-replay-hide class:

    <div class='session-replay-hide'>
       This will be hidden.
    </div>

    All inputs are hidden by default except button and submit. If some hidden inputs should be recorded, make them part of the CSS .session-replay-show class:

    <input type="text" class='session-replay-show'>

Get the state

Kotlin
SplunkRum.instance.sessionReplay.state
Java
SessionReplay.getInstance().getState();

Get the rendering mode

Kotlin
val state = SplunkRum.instance.sessionReplay.state
val renderingMode: RenderingMode = state.renderingMode
Java
State state = SessionReplay.getInstance().getState();
RenderingMode renderingMode = state.getRenderingMode();

Get the recording status

Kotlin
val status = SplunkRum.instance.state.status

when (status) {
    is Status.Recording -> {
        println("Recording in progress...")
    }
    is Status.NotRecording.NotStarted -> {
        println("Recording has not started.")
    }
    is Status.NotRecording.Stopped -> {
        println("Recording was stopped.")
    }
    is Status.NotRecording.BelowMinSDKVersion -> {
        println("Cannot record: below minimum SDK version.")
    }
    is Status.NotRecording.StorageLimitReached -> {
        println("Cannot record: storage limit reached.")
    }
    is Status.NotRecording.InternalError -> {
        println("Cannot record: internal error.")
    }
}

if (status.isRecording) {
    println("Do something only if we are recording")
}
Java
Status status = SessionReplay.getInstance().getState().getStatus();

if (status instanceof Status.Recording) {
    System.out.println("Recording...");
} else if (status instanceof Status.NotRecording.NotStarted) {
    System.out.println("Not started.");
} else if (status instanceof Status.NotRecording.Stopped) {
    System.out.println("Stopped.");
} else if (status instanceof Status.NotRecording.BelowMinSDKVersion) {
    System.out.println("Below SDK.");
} else if (status instanceof Status.NotRecording.StorageLimitReached) {
    System.out.println("Storage limit reached.");
} else if (status instanceof Status.NotRecording.InternalError) {
    System.out.println("Internal error.");
}

if (status.getIsRecording()) {
    System.out.println("Do something because it's recording");
}

Procedure

  1. Get and instance of the sessionReplay module.

  2. Set the module's rendering mode, recording mask, and sensitivity.

  3. Start recording.

Results

The sessionReplay module autonomously captures session data at its own rate and periodically publishes the data, typically several times per minute, through its API. To replay a user session, use replay player in the Splunk RUM UI.