Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add tests
  • Loading branch information
romtsn committed Aug 6, 2024
commit 6a2572e4681c08eea510496de5af70638f8ef0ef
15 changes: 13 additions & 2 deletions sentry-android-replay/api/sentry-android-replay.api
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public final class io/sentry/android/replay/ReplayCache$Companion {
public final fun makeReplayCacheDir (Lio/sentry/SentryOptions;Lio/sentry/protocol/SentryId;)Ljava/io/File;
}

public final class io/sentry/android/replay/ReplayIntegration : android/content/ComponentCallbacks, io/sentry/Integration, io/sentry/ReplayController, io/sentry/android/replay/ScreenshotRecorderCallback, io/sentry/android/replay/TouchRecorderCallback, java/io/Closeable {
public final class io/sentry/android/replay/ReplayIntegration : android/content/ComponentCallbacks, io/sentry/Integration, io/sentry/ReplayController, io/sentry/android/replay/ScreenshotRecorderCallback, io/sentry/android/replay/gestures/TouchRecorderCallback, java/io/Closeable {
public fun <init> (Landroid/content/Context;Lio/sentry/transport/ICurrentDateProvider;)V
public fun <init> (Landroid/content/Context;Lio/sentry/transport/ICurrentDateProvider;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
public synthetic fun <init> (Landroid/content/Context;Lio/sentry/transport/ICurrentDateProvider;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down Expand Up @@ -103,7 +103,18 @@ public final class io/sentry/android/replay/ScreenshotRecorderConfig$Companion {
public final fun from (Landroid/content/Context;Lio/sentry/SentryReplayOptions;)Lio/sentry/android/replay/ScreenshotRecorderConfig;
}

public abstract interface class io/sentry/android/replay/TouchRecorderCallback {
public final class io/sentry/android/replay/gestures/GestureRecorder : io/sentry/android/replay/OnRootViewsChangedListener {
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/android/replay/gestures/TouchRecorderCallback;)V
public fun onRootViewsChanged (Landroid/view/View;Z)V
public final fun stop ()V
}

public final class io/sentry/android/replay/gestures/ReplayGestureConverter {
public fun <init> (Lio/sentry/transport/ICurrentDateProvider;)V
public final fun convert (Landroid/view/MotionEvent;Lio/sentry/android/replay/ScreenshotRecorderConfig;)Ljava/util/List;
}

public abstract interface class io/sentry/android/replay/gestures/TouchRecorderCallback {
public abstract fun onTouchEvent (Landroid/view/MotionEvent;)V
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,18 @@ public data class ScreenshotRecorderConfig(
val frameRate: Int,
val bitRate: Int
) {
internal constructor(
scaleFactorX: Float,
scaleFactorY: Float
) : this(
recordingWidth = 0,
recordingHeight = 0,
scaleFactorX = scaleFactorX,
scaleFactorY = scaleFactorY,
frameRate = 0,
bitRate = 0
)

companion object {
/**
* Since codec block size is 16, so we have to adjust the width and height to it, otherwise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ internal class WindowRecorder(
capturingTask = capturer.scheduleAtFixedRateSafely(
options,
"$TAG.capture",
100L,
100L, // delay the first run by a bit, to allow root view listener to register
1000L / recorderConfig.frameRate,
MILLISECONDS
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class GestureRecorder(
}
}

private class SentryReplayGestureRecorder(
internal class SentryReplayGestureRecorder(
private val options: SentryOptions,
private val touchRecorderCallback: TouchRecorderCallback?,
delegate: Window.Callback?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package io.sentry.android.replay.gestures

import android.R
import android.app.Activity
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.widget.LinearLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.SentryOptions
import io.sentry.android.core.internal.gestures.NoOpWindowCallback
import io.sentry.android.replay.gestures.GestureRecorder.SentryReplayGestureRecorder
import io.sentry.android.replay.phoneWindow
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.annotation.Config
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

@RunWith(AndroidJUnit4::class)
@Config(sdk = [30])
class GestureRecorderTest {
internal class Fixture {

val options = SentryOptions()

fun getSut(
touchRecorderCallback: TouchRecorderCallback = NoOpTouchRecorderCallback()
): GestureRecorder {
return GestureRecorder(options, touchRecorderCallback)
}
}

private val fixture = Fixture()
private class NoOpTouchRecorderCallback : TouchRecorderCallback {
override fun onTouchEvent(event: MotionEvent) = Unit
}

@Test
fun `when new window added and window callback is already wrapped, does not wrap it again`() {
val activity = Robolectric.buildActivity(TestActivity::class.java).setup().get()
val gestureRecorder = fixture.getSut()

activity.root.phoneWindow?.callback = SentryReplayGestureRecorder(fixture.options, null, null)
gestureRecorder.onRootViewsChanged(activity.root, true)

assertFalse((activity.root.phoneWindow?.callback as SentryReplayGestureRecorder).delegate is SentryReplayGestureRecorder)
}

@Test
fun `when new window added tracks touch events`() {
var called = false
val activity = Robolectric.buildActivity(TestActivity::class.java).setup().get()
val motionEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
val gestureRecorder = fixture.getSut(
touchRecorderCallback = object : TouchRecorderCallback {
override fun onTouchEvent(event: MotionEvent) {
assertEquals(MotionEvent.ACTION_DOWN, event.action)
called = true
}
}
)

gestureRecorder.onRootViewsChanged(activity.root, true)

activity.root.phoneWindow?.callback?.dispatchTouchEvent(motionEvent)
assertTrue(called)
}

@Test
fun `when window removed and window is not sentry recorder does nothing`() {
val activity = Robolectric.buildActivity(TestActivity::class.java).setup().get()
val gestureRecorder = fixture.getSut()

activity.root.phoneWindow?.callback = NoOpWindowCallback()
gestureRecorder.onRootViewsChanged(activity.root, false)

assertTrue(activity.root.phoneWindow?.callback is NoOpWindowCallback)
}

@Test
fun `when window removed stops tracking touch events`() {
val activity = Robolectric.buildActivity(TestActivity::class.java).setup().get()
val gestureRecorder = fixture.getSut()

gestureRecorder.onRootViewsChanged(activity.root, true)
gestureRecorder.onRootViewsChanged(activity.root, false)

assertFalse(activity.root.phoneWindow?.callback is SentryReplayGestureRecorder)
}

@Test
fun `when stopped stops tracking all windows`() {
val activity1 = Robolectric.buildActivity(TestActivity::class.java).setup().get()
val activity2 = Robolectric.buildActivity(TestActivity2::class.java).setup().get()
val gestureRecorder = fixture.getSut()

gestureRecorder.onRootViewsChanged(activity1.root, true)
gestureRecorder.onRootViewsChanged(activity2.root, true)
gestureRecorder.stop()

assertFalse(activity1.root.phoneWindow?.callback is SentryReplayGestureRecorder)
assertFalse(activity2.root.phoneWindow?.callback is SentryReplayGestureRecorder)
}
}

private class TestActivity : Activity() {
lateinit var root: View

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(R.style.Theme_Holo_Light)
root = LinearLayout(this)
setContentView(root)
actionBar!!.setIcon(R.drawable.ic_lock_power_off)
}
}

private class TestActivity2 : Activity() {
lateinit var root: View

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(R.style.Theme_Holo_Light)
root = LinearLayout(this)
setContentView(root)
actionBar!!.setIcon(R.drawable.ic_lock_power_off)
}
}
Loading