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()
- }
-}