Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 0 additions & 3 deletions papa/api/papa.api
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,6 @@ public final class papa/InteractionTrace$Companion {

public abstract interface class papa/InteractionTrigger {
public static final field Companion Lpapa/InteractionTrigger$Companion;
public abstract fun equals (Ljava/lang/Object;)Z
public abstract fun getName ()Ljava/lang/String;
public abstract fun getTriggerUptime-UwyO8pc ()J
public abstract fun takeOverInteractionTrace ()Lpapa/InteractionTrace;
Expand All @@ -322,7 +321,6 @@ public final class papa/InteractionTrigger$Companion {

public final class papa/InteractionTriggerWithPayload : papa/InteractionTrigger {
public synthetic fun <init> (JLjava/lang/String;Lpapa/InteractionTrace;Ljava/lang/Object;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun equals (Ljava/lang/Object;)Z
public fun getName ()Ljava/lang/String;
public final fun getPayload ()Ljava/lang/Object;
public fun getTriggerUptime-UwyO8pc ()J
Expand Down Expand Up @@ -584,7 +582,6 @@ public final class papa/SentEvent {
public final class papa/SimpleInteractionTrigger : papa/InteractionTrigger {
public synthetic fun <init> (JLjava/lang/String;Lpapa/InteractionTrace;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (JLjava/lang/String;Lpapa/InteractionTrace;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun equals (Ljava/lang/Object;)Z
public fun getName ()Ljava/lang/String;
public fun getTriggerUptime-UwyO8pc ()J
public fun takeOverInteractionTrace ()Lpapa/InteractionTrace;
Expand Down
12 changes: 12 additions & 0 deletions papa/src/main/java/papa/InputEventTrigger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ class InputEventTrigger private constructor(
})
return trigger
}

/**
* Test-only helper for callers that need an [InputEventTrigger] payload without also wiring
* frame-render tracking through a real [Window]. This keeps unit tests focused on stack and
* payload behavior instead of Kotlin/JVM constructor details or Android frame callbacks.
*/
internal fun createForTest(
inputEvent: InputEvent,
deliveryUptime: Duration
): InputEventTrigger {
return InputEventTrigger(inputEvent, deliveryUptime)
}
}
}

Expand Down
14 changes: 0 additions & 14 deletions papa/src/main/java/papa/InteractionTrigger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ sealed interface InteractionTrigger {
val name: String
fun takeOverInteractionTrace(): InteractionTrace?

override fun equals(other: Any?): Boolean

companion object {
fun triggerNow(
name: String
Expand Down Expand Up @@ -37,12 +35,6 @@ class SimpleInteractionTrigger(
}
}

override fun equals(other: Any?): Boolean {
if (other == null) return false
if (other !is SimpleInteractionTrigger) return false
return other.name == name && other.triggerUptime == triggerUptime
}

override fun toString(): String {
return "InteractionTrigger(name='$name', triggerUptime=$triggerUptime)"
}
Expand All @@ -57,10 +49,4 @@ class InteractionTriggerWithPayload<T>(
override fun toString(): String {
return "InteractionTrigger(name='$name', triggerUptime=$triggerUptime, payload=$payload)"
}

override fun equals(other: Any?): Boolean {
if (other == null) return false
if (other !is InteractionTriggerWithPayload<*>) return false
return other.name == name && other.triggerUptime == triggerUptime
}
}
42 changes: 30 additions & 12 deletions papa/src/main/java/papa/MainThreadTriggerStack.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,39 @@ package papa

object MainThreadTriggerStack {

/**
* Returns the trigger with the earliest (minimum) [InteractionTrigger.triggerUptime], or `null`
* if the stack is empty.
*
* When duplicate triggers with identical uptime coexist on the stack, the most recently pushed
* one is preferred.
*
* Uses [reduceOrNull] for a single O(n) pass with zero allocations. On an equal-uptime tie the
* later element wins. This is optimal - finding a minimum in an unsorted collection requires
* examining every element at least once.
*/
val earliestInteractionTrigger: InteractionTrigger?
get() {
Handlers.checkOnMainThread()
return interactionTriggerStack.minByOrNull { it.triggerUptime }
return interactionTriggerStack.reduceOrNull { acc, trigger ->
if (trigger.triggerUptime <= acc.triggerUptime) {
trigger
} else {
acc
}
}
}

/**
* Returns the input-event triggers currently visible on the stack in stack order.
*
* This is a filtered view of [interactionTriggerStack]. Duplicate triggers are returned in
* their current stack order.
*/
val inputEventInteractionTriggers: List<InteractionTriggerWithPayload<InputEventTrigger>>
get() {
Handlers.checkOnMainThread()
return interactionTriggerStack.mapNotNull {
it.toInputEventTriggerOrNull()
}
return interactionTriggerStack.mapNotNull { it.toInputEventTriggerOrNull() }
}

private val interactionTriggerStack = mutableListOf<InteractionTrigger>()
Expand All @@ -31,8 +52,9 @@ object MainThreadTriggerStack {

/**
* Must be called from the main thread.
* Adds [trigger] to the [interactionTriggerStack], it will replace any existing trigger with
* the same [InteractionTrigger.name] and [InteractionTrigger.triggerUptime].
* Adds [trigger] to the [interactionTriggerStack] for the duration of [block]. Duplicate
* trigger instances intentionally coexist on the stack so nested scopes do not evict an earlier
* instance that is still responsible for later cleanup.
*
* @param endTraceAfterBlock Finish the interaction trace after [block] runs.
* @param block The code to run, during whose call stack the trigger added will be available
Expand All @@ -44,15 +66,11 @@ object MainThreadTriggerStack {
block: () -> T
): T {
Handlers.checkOnMainThread()
// First, remove based on object equality (which uses name/triggerUptime). This has the effect
// of replacing any existing same-named, same-timed triggers.
// After the block() we remove just this instance from the stack.
interactionTriggerStack.removeAll { it == trigger }
interactionTriggerStack.add(trigger)
pushTriggeredBy(trigger)
try {
return block()
} finally {
interactionTriggerStack.removeAll { it === trigger }
popTriggeredBy(trigger)
if (endTraceAfterBlock) {
trigger.takeOverInteractionTrace()?.endTrace()
}
Expand Down
Loading
Loading