Refactor TestEmitter and it doesn't need to be MainThreadInitializedObject

Bug: 361850561
Test: Presubmit
Flag: EXEMPT dagger

Change-Id: I30e7c3c165db698785e9e4d7e2256ab061ff71a2
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 315301a..db79662 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -182,8 +182,8 @@
 import com.android.launcher3.celllayout.CellPosMapper.TwoPanelCellPosMapper;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.debug.TestEvent;
 import com.android.launcher3.debug.TestEventEmitter;
+import com.android.launcher3.debug.TestEventEmitter.TestEvent;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -610,7 +610,7 @@
             RuleController.getInstance(this).setRules(
                     RuleController.parseRules(this, R.xml.split_configuration));
         }
-        TestEventEmitter.INSTANCE.get(this).sendEvent(TestEvent.LAUNCHER_ON_CREATE);
+        TestEventEmitter.sendEvent(TestEvent.LAUNCHER_ON_CREATE);
     }
 
     protected ModelCallbacks createModelCallbacks() {
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 5338fb4..d01f35d 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -11,8 +11,8 @@
 import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
 import com.android.launcher3.allapps.AllAppsStore
 import com.android.launcher3.config.FeatureFlags
-import com.android.launcher3.debug.TestEvent
 import com.android.launcher3.debug.TestEventEmitter
+import com.android.launcher3.debug.TestEventEmitter.TestEvent
 import com.android.launcher3.model.BgDataModel
 import com.android.launcher3.model.StringCache
 import com.android.launcher3.model.data.AppInfo
@@ -154,7 +154,7 @@
             /*pause=*/ false,
             deviceProfile.isTwoPanels,
         )
-        TestEventEmitter.INSTANCE.get(launcher).sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
+        TestEventEmitter.sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
     }
 
     /**
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index b41a425..cfb1161 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -80,8 +80,8 @@
 import com.android.launcher3.celllayout.CellPosMapper;
 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.debug.TestEvent;
 import com.android.launcher3.debug.TestEventEmitter;
+import com.android.launcher3.debug.TestEventEmitter.TestEvent;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
@@ -2255,7 +2255,7 @@
         if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
             d.stateAnnouncer.completeAction(R.string.item_moved);
         }
-        TestEventEmitter.INSTANCE.get(getContext()).sendEvent(TestEvent.WORKSPACE_ON_DROP);
+        TestEventEmitter.sendEvent(TestEvent.WORKSPACE_ON_DROP);
     }
 
     @Nullable
diff --git a/src/com/android/launcher3/debug/TestEventEmitter.java b/src/com/android/launcher3/debug/TestEventEmitter.java
new file mode 100644
index 0000000..ed3b4bb
--- /dev/null
+++ b/src/com/android/launcher3/debug/TestEventEmitter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.debug;
+
+/**
+ * TestEventsEmitter shouldn't do anything since it runs on the launcher code and not on
+ * tests. This is just a placeholder and test should mock the static sendEvent method.
+ * See "EventsRule.kt" in tests folder where sendEvent is statically mocked to change the
+ * behavior in tests.
+ */
+public class TestEventEmitter {
+    public static void sendEvent(TestEvent event) {
+    }
+
+    /** Events fired by the launcher. */
+    public enum TestEvent {
+
+        LAUNCHER_ON_CREATE("LAUNCHER_ON_CREATE"),
+        WORKSPACE_ON_DROP("WORKSPACE_ON_DROP"),
+        RESIZE_FRAME_SHOWING("RESIZE_FRAME_SHOWING"),
+        WORKSPACE_FINISH_LOADING("WORKSPACE_FINISH_LOADING"),
+        SPRING_LOADED_STATE_STARTED("SPRING_LOADED_STATE_STARTED"),
+        SPRING_LOADED_STATE_COMPLETED("SPRING_LOADED_STATE_COMPLETED");
+
+        TestEvent(String event) {
+        }
+
+    }
+}
+
+
diff --git a/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt b/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
deleted file mode 100644
index 52b454f..0000000
--- a/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.debug
-
-import android.content.Context
-import android.util.Log
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.SafeCloseable
-
-/** Events fired by the launcher. */
-enum class TestEvent(val event: String) {
-    LAUNCHER_ON_CREATE("LAUNCHER_ON_CREATE"),
-    WORKSPACE_ON_DROP("WORKSPACE_ON_DROP"),
-    RESIZE_FRAME_SHOWING("RESIZE_FRAME_SHOWING"),
-    WORKSPACE_FINISH_LOADING("WORKSPACE_FINISH_LOADING"),
-    SPRING_LOADED_STATE_STARTED("SPRING_LOADED_STATE_STARTED"),
-    SPRING_LOADED_STATE_COMPLETED("SPRING_LOADED_STATE_COMPLETED"),
-}
-
-/** Interface to create TestEventEmitters. */
-interface TestEventEmitter : SafeCloseable {
-
-    companion object {
-        @JvmField
-        val INSTANCE =
-            MainThreadInitializedObject<TestEventEmitter> { _: Context? ->
-                TestEventsEmitterProduction()
-            }
-    }
-
-    fun sendEvent(event: TestEvent)
-}
-
-/**
- * TestEventsEmitterProduction shouldn't do anything since it runs on the launcher code and not on
- * tests. This is just a placeholder and test should override this class.
- */
-class TestEventsEmitterProduction : TestEventEmitter {
-
-    override fun close() {}
-
-    override fun sendEvent(event: TestEvent) {
-        Log.d("TestEventsEmitterProduction", "Event sent ${event.event}")
-    }
-}
diff --git a/tests/Android.bp b/tests/Android.bp
index 4bc654c..fc08e86 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -168,6 +168,7 @@
         "src/**/*Test.java",
         "src/**/*Test.kt",
         "src/**/RoboApiWrapper.kt",
+        "src/**/EventsRule.kt",
         "multivalentTests/src/**/*Test.java",
         "multivalentTests/src/**/*Test.kt",
     ],
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt
new file mode 100644
index 0000000..20ad60f
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.celllayout.integrationtest.events
+
+import com.android.launcher3.debug.TestEventEmitter.TestEvent
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeoutOrNull
+
+enum class EventStatus() {
+    SUCCESS,
+    FAILURE,
+    TIMEOUT,
+}
+
+class EventWaiter(val eventToWait: TestEvent) {
+    private val deferrable = CompletableDeferred<EventStatus>()
+
+    companion object {
+        private const val TAG = "EventWaiter"
+        private val SIGNAL_TIMEOUT = TimeUnit.SECONDS.toMillis(5)
+    }
+
+    fun waitForSignal(timeout: Long = SIGNAL_TIMEOUT) = runBlocking {
+        var status = withTimeoutOrNull(timeout) { deferrable.await() }
+        if (status == null) {
+            status = EventStatus.TIMEOUT
+        }
+        if (status != EventStatus.SUCCESS) {
+            throw Exception("Failure waiting for event $eventToWait, failure = $status")
+        }
+    }
+
+    fun terminate() {
+        deferrable.complete(EventStatus.SUCCESS)
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt
index fb61ced..45eb5e1 100644
--- a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt
@@ -17,11 +17,15 @@
 package com.android.launcher3.celllayout.integrationtest.events
 
 import android.content.Context
-import com.android.launcher3.debug.TestEvent
+import android.util.Log
+import com.android.dx.mockito.inline.extended.ExtendedMockito.*
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.launcher3.debug.TestEventEmitter
+import com.android.launcher3.debug.TestEventEmitter.TestEvent
 import org.junit.rules.TestRule
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
+import org.mockito.quality.Strictness
 
 /**
  * Rule to create EventWaiters to wait for events that happens on the Launcher. For reference look
@@ -30,35 +34,65 @@
  * Waiting for event should be used to prevent race conditions, it provides a more precise way of
  * waiting for events compared to [AbstractLauncherUiTest#waitForLauncherCondition].
  *
- * This class overrides the [TestEventEmitter] with [TestEventsEmitterImplementation] and makes sure
- * to return the [TestEventEmitter] to the previous value when finished.
+ * This class mocks the static method [TestEventEmitter.sendEvent]
  */
 class EventsRule(val context: Context) : TestRule {
 
-    private var prevEventEmitter: TestEventEmitter? = null
+    private val expectedEvents: ArrayDeque<EventWaiter> = ArrayDeque()
 
-    private val eventEmitter = TestEventsEmitterImplementation()
+    private lateinit var mockitoSession: StaticMockitoSession
 
     override fun apply(base: Statement, description: Description?): Statement {
         return object : Statement() {
             override fun evaluate() {
-                beforeTest()
-                base.evaluate()
-                afterTest()
+                try {
+                    beforeTest()
+                    base.evaluate()
+                } finally {
+                    afterTest()
+                }
             }
         }
     }
 
     fun createEventWaiter(expectedEvent: TestEvent): EventWaiter {
-        return eventEmitter.createEventWaiter(expectedEvent)
+        val eventWaiter = EventWaiter(expectedEvent)
+        expectedEvents.add(eventWaiter)
+        return eventWaiter
     }
 
     private fun beforeTest() {
-        prevEventEmitter = TestEventEmitter.INSTANCE.get(context)
-        TestEventEmitter.INSTANCE.initializeForTesting(eventEmitter)
+        mockitoSession =
+            mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(TestEventEmitter::class.java)
+                .startMocking()
+
+        doAnswer { invocation ->
+                val event = (invocation.arguments[0] as TestEvent)
+                Log.d(TAG, "Signal received $event")
+                Log.d(TAG, "Total expected events ${expectedEvents.size}")
+                if (!expectedEvents.isEmpty()) {
+                    val eventWaiter = expectedEvents.last()
+                    if (eventWaiter.eventToWait == event) {
+                        Log.d(TAG, "Removing $event")
+                        expectedEvents.removeLast()
+                        eventWaiter.terminate()
+                    } else {
+                        Log.d(TAG, "Not matching $event")
+                    }
+                }
+                null
+            }
+            .`when` { TestEventEmitter.sendEvent(any()) }
     }
 
     private fun afterTest() {
-        TestEventEmitter.INSTANCE.initializeForTesting(prevEventEmitter)
+        mockitoSession.finishMocking()
+        expectedEvents.clear()
+    }
+
+    companion object {
+        private const val TAG = "TestEvents"
     }
 }
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt
deleted file mode 100644
index 5e062d0..0000000
--- a/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.celllayout.integrationtest.events
-
-import android.util.Log
-import com.android.launcher3.debug.TestEvent
-import com.android.launcher3.debug.TestEventEmitter
-import java.util.concurrent.TimeUnit
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withTimeoutOrNull
-
-enum class EventStatus() {
-    SUCCESS,
-    FAILURE,
-    TIMEOUT,
-}
-
-class EventWaiter(val eventToWait: TestEvent) {
-    private val deferrable = CompletableDeferred<EventStatus>()
-
-    companion object {
-        private const val TAG = "EventWaiter"
-        private val SIGNAL_TIMEOUT = TimeUnit.SECONDS.toMillis(5)
-    }
-
-    fun waitForSignal(timeout: Long = SIGNAL_TIMEOUT) = runBlocking {
-        var status = withTimeoutOrNull(timeout) { deferrable.await() }
-        if (status == null) {
-            status = EventStatus.TIMEOUT
-        }
-        if (status != EventStatus.SUCCESS) {
-            throw Exception("Failure waiting for event $eventToWait, failure = $status")
-        }
-    }
-
-    fun terminate() {
-        deferrable.complete(EventStatus.SUCCESS)
-    }
-}
-
-class TestEventsEmitterImplementation() : TestEventEmitter {
-    companion object {
-        private const val TAG = "TestEvents"
-    }
-
-    private val expectedEvents: ArrayDeque<EventWaiter> = ArrayDeque()
-
-    fun createEventWaiter(expectedEvent: TestEvent): EventWaiter {
-        val eventWaiter = EventWaiter(expectedEvent)
-        expectedEvents.add(eventWaiter)
-        return eventWaiter
-    }
-
-    private fun clearQueue() {
-        expectedEvents.clear()
-    }
-
-    override fun sendEvent(event: TestEvent) {
-        Log.d(TAG, "Signal received $event")
-        Log.d(TAG, "Total expected events ${expectedEvents.size}")
-        if (expectedEvents.isEmpty()) return
-        val eventWaiter = expectedEvents.last()
-        if (eventWaiter.eventToWait == event) {
-            Log.d(TAG, "Removing $event")
-            expectedEvents.removeLast()
-            eventWaiter.terminate()
-        } else {
-            Log.d(TAG, "Not matching $event")
-        }
-    }
-
-    override fun close() {
-        clearQueue()
-    }
-}