diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 315301a9bb..db79662216 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.CellPos; 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 @@ public class Launcher extends StatefulActivity 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 5338fb435f..d01f35dc83 100644 --- a/src/com/android/launcher3/ModelCallbacks.kt +++ b/src/com/android/launcher3/ModelCallbacks.kt @@ -11,8 +11,8 @@ import com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET 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 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { /*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 b41a425846..cfb116147c 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -80,8 +80,8 @@ import com.android.launcher3.celllayout.CellLayoutLayoutParams; 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 @@ public class Workspace extends PagedView 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 0000000000..ed3b4bbac9 --- /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 52b454f2b7..0000000000 --- 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 { _: 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 4bc654c7d0..fc08e86284 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -168,6 +168,7 @@ filegroup { "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/TestEventsEmitterImplementation.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt similarity index 58% rename from tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt rename to tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt index 5e062d0584..20ad60f11b 100644 --- a/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt +++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt @@ -16,9 +16,7 @@ package com.android.launcher3.celllayout.integrationtest.events -import android.util.Log -import com.android.launcher3.debug.TestEvent -import com.android.launcher3.debug.TestEventEmitter +import com.android.launcher3.debug.TestEventEmitter.TestEvent import java.util.concurrent.TimeUnit import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.runBlocking @@ -52,39 +50,3 @@ class EventWaiter(val eventToWait: TestEvent) { deferrable.complete(EventStatus.SUCCESS) } } - -class TestEventsEmitterImplementation() : TestEventEmitter { - companion object { - private const val TAG = "TestEvents" - } - - private val expectedEvents: ArrayDeque = 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() - } -} 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 fb61cedaa8..45eb5e1889 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 @@ import org.junit.runners.model.Statement * 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 = 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" } }