Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 'lateinit property currentTrigger has not been initialized' on WIKO devices #70

Merged
merged 2 commits into from
Oct 26, 2024
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
1 change: 1 addition & 0 deletions papa-main-trace/api/papa-main-trace.api
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public final class papa/MainThreadMessageSpy {
public final fun addTracer (Lpapa/MainThreadMessageSpy$Tracer;)V
public final fun getCurrentMessageAsString ()Ljava/lang/String;
public final fun getEnabled ()Z
public final fun isInMainThreadMessage ()Z
public final fun removeTracer (Lpapa/MainThreadMessageSpy$Tracer;)V
public final fun startSpyingMainThreadDispatching ()V
public final fun stopSpyingMainThreadDispatching ()V
Expand Down
2 changes: 1 addition & 1 deletion papa-main-trace/src/main/java/papa/Handlers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object Handlers {
*/
fun onCurrentMainThreadMessageFinished(block: () -> Unit) {
checkOnMainThread()
if (MainThreadMessageSpy.enabled) {
if (MainThreadMessageSpy.isInMainThreadMessage) {
MainThreadMessageSpy.onCurrentMessageFinished(block)
} else {
mainThreadHandler.postAtFrontOfQueueAsync(block)
Expand Down
23 changes: 21 additions & 2 deletions papa-main-trace/src/main/java/papa/MainThreadMessageSpy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ object MainThreadMessageSpy {
var enabled = false
private set

val isInMainThreadMessage: Boolean
get() = enabled && currentMessageAsString != null

/**
* Must be called only from the main thread.
* Null if [enabled] is false or if the code calling this (from the main thread) is running
* from outside the dispatching of a main thread message. For example,
* [MessageQueue.nativePollOnce] may invoke input event dispatching code directly.
*/
var currentMessageAsString: String? = null
private set
get() {
Expand Down Expand Up @@ -59,9 +68,18 @@ object MainThreadMessageSpy {
// Looper can log to a printer before and after each message. We leverage this to surface the
// beginning and end of every main thread message in system traces. This costs a few extra string
// concatenations for each message handling.
// The printer is called before ('>>' prefix) and after ('<<' prefix) every message.

// Looper.mLogging is extracted to a local variable inside the message loop, and the same
// reference is used for before and after. We're setting Looper.mLogging in the middle of a
// message but won't get the "finish" callback because that local variable still references
// null at that point.
var before = true
Looper.getMainLooper().setMessageLogging { messageAsString ->
val before = messageAsString.startsWith('>')
if (!enabled) {
// We still get called here for the last message finishing after we called
// stopSpyingMainThreadDispatching which called Looper.setMessageLogging(null)
return@setMessageLogging
}
if (before) {
currentMessageAsString = messageAsString
}
Expand All @@ -71,6 +89,7 @@ object MainThreadMessageSpy {
if (!before) {
currentMessageAsString = null
}
before = !before
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something's wrong with the logic, not sure what yet.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}
}

Expand Down
44 changes: 44 additions & 0 deletions papa/src/androidTest/java/papa/test/MainThreadMessageSpyTest.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package papa.test

import android.os.Build.VERSION
import android.view.Choreographer
import android.view.MotionEvent
import android.widget.Button
import androidx.test.core.app.ActivityScenario
Expand Down Expand Up @@ -78,6 +79,49 @@ class MainThreadMessageSpyTest {
.inOrder()
}

@Test fun inputEventDispatching_not_in_MainThread_Message() {
var spyEnabled: Boolean? = null
var isInMainThreadMessage: Boolean? = null
var currentMessageAsString: String? = null

val handledTap = CountDownLatch(1)
ActivityScenario.launch(TestActivity::class.java).use { scenario ->

val blockFrame = CountDownLatch(1)
val waitForFrame = CountDownLatch(1)
scenario.onActivity { activity ->
activity.setContentView(Button(activity).apply {
text = "Click Me"
setOnTouchListener { _, _ ->
spyEnabled = MainThreadMessageSpy.enabled
isInMainThreadMessage = MainThreadMessageSpy.isInMainThreadMessage
currentMessageAsString = MainThreadMessageSpy.currentMessageAsString
handledTap.countDown()
false
}
})
Choreographer.getInstance().postFrameCallback {
// unblock clicking code.
waitForFrame.countDown()
check(blockFrame.await(5, SECONDS))
// Giving a little time to enqueue the tap.
Thread.sleep(500)
}
}

check(waitForFrame.await(5, SECONDS))
// unblock frame, at this point tap should be enqueued and dequeued immediately, and not
// during a choreographer frame.
blockFrame.countDown()
onView(withText("Click Me")).location.sendTap()

check(handledTap.await(5, SECONDS))
assertThat(spyEnabled).isTrue()
assertThat(currentMessageAsString).isNull()
assertThat(isInMainThreadMessage).isFalse()
}
}

@Test
fun no_ConcurrentModificationException_when_iterating_after_onCurrentMessageFinished_removes_its_tracer() {
runOnMainSync {
Expand Down
6 changes: 5 additions & 1 deletion papa/src/main/java/papa/Choreographers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ object Choreographers {
private val pendingRenderedCallbacks = mutableListOf<OnFrameRenderedListener>()

private val isInChoreographerFrameMessage by mainThreadMessageScopedLazy {
"android.view.Choreographer\$FrameDisplayEventReceiver" in MainThreadMessageSpy.currentMessageAsString!!
if (MainThreadMessageSpy.isInMainThreadMessage) {
"android.view.Choreographer\$FrameDisplayEventReceiver" in MainThreadMessageSpy.currentMessageAsString!!
} else {
false
}
}

/**
Expand Down
Loading