From 224f67a046cd5f9970a067d71753809a7f59bd45 Mon Sep 17 00:00:00 2001 From: Sebastian Franco Date: Thu, 18 Apr 2024 18:40:40 -0700 Subject: [PATCH] Moving ReorderWidgetsTest to an integration test instead of E2E Flag: TEST_ONLY Test: IntegrationReorderWidgetsTest Bug: 324261526 Fix: 343761044 Change-Id: I9bfe8b587e1024260ddeeb48157c3d204f2d58f2 --- .../launcher3/AppWidgetResizeFrame.java | 5 + src/com/android/launcher3/Launcher.java | 4 +- src/com/android/launcher3/ModelCallbacks.kt | 3 + src/com/android/launcher3/Workspace.java | 4 +- .../debug/TestEventsEmitterProduction.kt | 54 +++ .../launcher3/dragndrop/DragController.java | 7 +- tests/assets/ReorderWidgets/full_reorder_case | 8 +- tests/assets/ReorderWidgets/push_reorder_case | 24 +- .../assets/ReorderWidgets/simple_reorder_case | 2 +- .../celllayout/CellLayoutTestCaseReader.java | 2 +- .../celllayout/TaplReorderWidgetsTest.java | 312 ------------------ .../celllayout/integrationtest/TestUtils.kt | 72 ++++ .../integrationtest/events/EventsRule.kt | 64 ++++ .../events/TestEventsEmitterImplementation.kt | 89 +++++ 14 files changed, 316 insertions(+), 334 deletions(-) create mode 100644 src/com/android/launcher3/debug/TestEventsEmitterProduction.kt delete mode 100644 tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java create mode 100644 tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt create mode 100644 tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt create mode 100644 tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index b51e850c04..ef56246136 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -37,6 +37,8 @@ import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.celllayout.CellLayoutLayoutParams; 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.dragndrop.DragLayer; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logging.InstanceId; @@ -221,6 +223,9 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O dl.addView(frame); frame.mIsOpen = true; frame.post(() -> frame.snapToWidget(false)); + TestEventEmitter.INSTANCE.get(widget.getContext()).sendEvent( + TestEvent.RESIZE_FRAME_SHOWING + ); } private void setCornerRadiusFromWidget() { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index d90580158b..6a2f536efc 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -166,7 +166,6 @@ import androidx.annotation.VisibleForTesting; import androidx.core.os.BuildCompat; import androidx.window.embedding.RuleController; -import com.android.launcher3.DeviceProfile; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.ActivityAllAppsContainerView; @@ -181,6 +180,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.dot.DotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; @@ -595,6 +596,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); } protected ModelCallbacks createModelCallbacks() { diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt index 13062b6b1b..83c34ce725 100644 --- a/src/com/android/launcher3/ModelCallbacks.kt +++ b/src/com/android/launcher3/ModelCallbacks.kt @@ -11,6 +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.model.BgDataModel import com.android.launcher3.model.StringCache import com.android.launcher3.model.data.AppInfo @@ -156,6 +158,7 @@ class ModelCallbacks(private var launcher: Launcher) : BgDataModel.Callbacks { /*pause=*/ false, deviceProfile.isTwoPanels ) + TestEventEmitter.INSTANCE.get(launcher).sendEvent(TestEvent.WORKSPACE_FINISH_LOADING) } /** diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index e601a3e298..2995e8a3ee 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -80,6 +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.dot.FolderDotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; @@ -314,7 +316,6 @@ public class Workspace extends PagedView */ public Workspace(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mLauncher = Launcher.getLauncher(context); mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this); mWallpaperManager = WallpaperManager.getInstance(context); @@ -2218,6 +2219,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); } @Nullable diff --git a/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt b/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt new file mode 100644 index 0000000000..650df5a840 --- /dev/null +++ b/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt @@ -0,0 +1,54 @@ +/* + * 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 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"), +} + +/** 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) {} +} diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index bc5a164da8..c50c008f30 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -27,6 +27,7 @@ import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.app.animation.Interpolators; import com.android.launcher3.DragSource; @@ -69,8 +70,9 @@ public abstract class DragController */ protected DragDriver mDragDriver = null; + @VisibleForTesting /** Options controlling the drag behavior. */ - protected DragOptions mOptions; + public DragOptions mOptions; /** Coordinate for motion down event */ protected final Point mMotionDown = new Point(); @@ -79,7 +81,8 @@ public abstract class DragController protected final Point mTmpPoint = new Point(); - protected DropTarget.DragObject mDragObject; + @VisibleForTesting + public DropTarget.DragObject mDragObject; /** Who can receive drop events */ private final ArrayList mDropTargets = new ArrayList<>(); diff --git a/tests/assets/ReorderWidgets/full_reorder_case b/tests/assets/ReorderWidgets/full_reorder_case index 850e4fdd0e..2890b79abf 100644 --- a/tests/assets/ReorderWidgets/full_reorder_case +++ b/tests/assets/ReorderWidgets/full_reorder_case @@ -17,12 +17,12 @@ # Test 4x4 board: 4x4 xxxx -bbmm +bbaa +iimm iimm -iiaa arguments: 0 2 board: 4x4 xxxx -bbii +bbaa mmii -mmaa \ No newline at end of file +mmii \ No newline at end of file diff --git a/tests/assets/ReorderWidgets/push_reorder_case b/tests/assets/ReorderWidgets/push_reorder_case index 8e845a2462..1eacfaec0f 100644 --- a/tests/assets/ReorderWidgets/push_reorder_case +++ b/tests/assets/ReorderWidgets/push_reorder_case @@ -17,28 +17,28 @@ #Test 5x5 board: 5x5 xxxxx -bbbm- +bbb-- --ccc --ddd ------ -arguments: 2 1 +----m +arguments: 2 2 board: 5x5 xxxxx ---m-- bbb-- +--m-- --ccc --ddd #6x5 Test board: 6x5 xxxxxx -bbbbm- ---aaa- ---ddd- ------- -arguments: 2 1 -board: 6x5 -xxxxxx ---m--- bbbb-- --aaa- +--ddd- +-----m +arguments: 2 2 +board: 6x5 +xxxxxx +bbbb-- +--m--- +--aaa- --ddd- \ No newline at end of file diff --git a/tests/assets/ReorderWidgets/simple_reorder_case b/tests/assets/ReorderWidgets/simple_reorder_case index 2c50ce4d68..991ccb55ef 100644 --- a/tests/assets/ReorderWidgets/simple_reorder_case +++ b/tests/assets/ReorderWidgets/simple_reorder_case @@ -21,7 +21,7 @@ xxxxx --mm- ----- ----- -arguments: 0 4 +arguments: 0 3 board: 5x5 xxxxx ----- diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java index 419cb3d52e..f1403e5d59 100644 --- a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java +++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java @@ -54,7 +54,7 @@ public class CellLayoutTestCaseReader { } public static class Arguments extends TestSection { - String[] arguments; + public String[] arguments; public Arguments(String[] arguments) { super(State.ARGUMENTS); diff --git a/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java b/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java deleted file mode 100644 index 28a1325944..0000000000 --- a/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2022 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; - -import static android.platform.uiautomator_helpers.DeviceHelpers.getContext; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.graphics.Point; -import android.net.Uri; -import android.util.Log; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.MultipageCellLayout; -import com.android.launcher3.celllayout.board.CellLayoutBoard; -import com.android.launcher3.celllayout.board.TestWorkspaceBuilder; -import com.android.launcher3.celllayout.board.WidgetRect; -import com.android.launcher3.tapl.Widget; -import com.android.launcher3.tapl.WidgetResizeFrame; -import com.android.launcher3.ui.AbstractLauncherUiTest; -import com.android.launcher3.util.ModelTestExtensions; -import com.android.launcher3.util.rule.ShellCommandRule; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class TaplReorderWidgetsTest extends AbstractLauncherUiTest { - - @Rule - public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); - - private static final String TAG = TaplReorderWidgetsTest.class.getSimpleName(); - - private static final List FOLDABLE_GRIDS = List.of("normal", "practical", "reasonable"); - - TestWorkspaceBuilder mWorkspaceBuilder; - - @Before - public void setup() throws Throwable { - mWorkspaceBuilder = new TestWorkspaceBuilder(mTargetContext); - super.setUp(); - } - - @After - public void tearDown() { - ModelTestExtensions.INSTANCE.clearModelDb( - LauncherAppState.getInstance(getContext()).getModel() - ); - } - - /** - * Validate if the given board represent the current CellLayout - **/ - private boolean validateBoard(List testBoards) { - ArrayList workspaceBoards = workspaceToBoards(); - if (workspaceBoards.size() < testBoards.size()) { - return false; - } - for (int i = 0; i < testBoards.size(); i++) { - if (testBoards.get(i).compareTo(workspaceBoards.get(i)) != 0) { - return false; - } - } - return true; - } - - private FavoriteItemsTransaction buildWorkspaceFromBoards(List boards, - FavoriteItemsTransaction transaction) { - for (int i = 0; i < boards.size(); i++) { - CellLayoutBoard board = boards.get(i); - mWorkspaceBuilder.buildFromBoard(board, transaction, i); - } - return transaction; - } - - private void printCurrentWorkspace() { - InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); - ArrayList boards = workspaceToBoards(); - for (int i = 0; i < boards.size(); i++) { - Log.d(TAG, "Screen number " + i); - Log.d(TAG, ".\n" + boards.get(i).toString(idp.numColumns, idp.numRows)); - } - } - - private ArrayList workspaceToBoards() { - return getFromLauncher(CellLayoutTestUtils::workspaceToBoards); - } - - private WidgetRect getWidgetClosestTo(Point point) { - ArrayList workspaceBoards = workspaceToBoards(); - int maxDistance = 9999; - WidgetRect bestRect = null; - for (int i = 0; i < workspaceBoards.get(0).getWidgets().size(); i++) { - WidgetRect widget = workspaceBoards.get(0).getWidgets().get(i); - if (widget.getCellX() == 0 && widget.getCellY() == 0) { - continue; - } - int distance = Math.abs(point.x - widget.getCellX()) - + Math.abs(point.y - widget.getCellY()); - if (distance == 0) { - break; - } - if (distance < maxDistance) { - maxDistance = distance; - bestRect = widget; - } - } - return bestRect; - } - - /** - * This function might be odd, its function is to select a widget and leave it in its place. - * The idea is to make the test broader and also test after a widgets resized because the - * underlying code does different things in that case - */ - private void triggerWidgetResize(ReorderTestCase testCase) { - WidgetRect widgetRect = getWidgetClosestTo(testCase.moveMainTo); - if (widgetRect == null) { - // Some test doesn't have a widget in the final position, in those cases we will ignore - // them - return; - } - Widget widget = mLauncher.getWorkspace().getWidgetAtCell(widgetRect.getCellX(), - widgetRect.getCellY()); - WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(widgetRect.getCellX(), - widgetRect.getCellY(), widgetRect.getSpanX(), widgetRect.getSpanY()); - resizeFrame.dismiss(); - } - - private void runTestCase(ReorderTestCase testCase) { - WidgetRect mainWidgetCellPos = CellLayoutBoard.getMainFromList( - testCase.mStart); - - FavoriteItemsTransaction transaction = - new FavoriteItemsTransaction(mTargetContext); - transaction = buildWorkspaceFromBoards(testCase.mStart, transaction); - transaction.commit(); - mLauncher.waitForLauncherInitialized(); - // resetLoaderState triggers the launcher to start loading the workspace which allows - // waitForLauncherCondition to wait for that condition, otherwise the condition would - // always be true and it wouldn't wait for the changes to be applied. - waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading()); - - triggerWidgetResize(testCase); - - Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.getCellX(), - mainWidgetCellPos.getCellY()); - assertNotNull(widget); - WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(testCase.moveMainTo.x, - testCase.moveMainTo.y, mainWidgetCellPos.getSpanX(), mainWidgetCellPos.getSpanY()); - resizeFrame.dismiss(); - - boolean isValid = false; - for (List boards : testCase.mEnd) { - isValid |= validateBoard(boards); - if (isValid) break; - } - printCurrentWorkspace(); - assertTrue("Non of the valid boards match with the current state", isValid); - } - - /** - * Run only the test define for the current grid size if such test exist - * - * @param testCaseMap map containing all the tests per grid size (Point) - */ - private boolean runTestCaseMap(Map testCaseMap, String testName) { - Point iconGridDimensions = mLauncher.getWorkspace().getIconGridDimensions(); - Log.d(TAG, "Running test " + testName + " for grid " + iconGridDimensions); - if (!testCaseMap.containsKey(iconGridDimensions)) { - Log.d(TAG, "The test " + testName + " doesn't support " + iconGridDimensions - + " grid layout"); - return false; - } - runTestCase(testCaseMap.get(iconGridDimensions)); - - return true; - } - - private void runTestCaseMapForAllGrids(Map testCaseMap, - String testName) { - boolean runAtLeastOnce = false; - for (String grid : FOLDABLE_GRIDS) { - applyGridOption(grid); - mLauncher.waitForLauncherInitialized(); - runAtLeastOnce |= runTestCaseMap(testCaseMap, testName); - } - Assume.assumeTrue("None of the grids are supported", runAtLeastOnce); - } - - private void applyGridOption(Object argValue) { - String testProviderAuthority = mTargetContext.getPackageName() + ".grid_control"; - Uri gridUri = new Uri.Builder() - .scheme(ContentResolver.SCHEME_CONTENT) - .authority(testProviderAuthority) - .appendPath("default_grid") - .build(); - ContentValues values = new ContentValues(); - values.putObject("name", argValue); - Assert.assertEquals(1, - mTargetContext.getContentResolver().update(gridUri, values, null, null)); - } - - @Test - public void simpleReorder() throws Exception { - runTestCaseMap(getTestMap("ReorderWidgets/simple_reorder_case"), - "push_reorder_case"); - } - - @Test - public void pushTest() throws Exception { - runTestCaseMap(getTestMap("ReorderWidgets/push_reorder_case"), - "push_reorder_case"); - } - - @Test - public void fullReorder() throws Exception { - runTestCaseMap(getTestMap("ReorderWidgets/full_reorder_case"), - "full_reorder_case"); - } - - @Test - public void moveOutReorder() throws Exception { - runTestCaseMap(getTestMap("ReorderWidgets/move_out_reorder_case"), - "move_out_reorder_case"); - } - - @Test - public void multipleCellLayoutsSimpleReorder() throws Exception { - Assume.assumeTrue("Test doesn't support foldables", getFromLauncher( - l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout)); - runTestCaseMapForAllGrids(getTestMap("ReorderWidgets/multiple_cell_layouts_simple_reorder"), - "multiple_cell_layouts_simple_reorder"); - } - - @Test - public void multipleCellLayoutsNoSpaceReorder() throws Exception { - Assume.assumeTrue("Test doesn't support foldables", getFromLauncher( - l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout)); - runTestCaseMapForAllGrids( - getTestMap("ReorderWidgets/multiple_cell_layouts_no_space_reorder"), - "multiple_cell_layouts_no_space_reorder"); - } - - @Test - public void multipleCellLayoutsReorderToOtherSide() throws Exception { - Assume.assumeTrue("Test doesn't support foldables", getFromLauncher( - l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout)); - runTestCaseMapForAllGrids( - getTestMap("ReorderWidgets/multiple_cell_layouts_reorder_other_side"), - "multiple_cell_layouts_reorder_other_side"); - } - - private void addTestCase(Iterator sections, - Map testCaseMap) { - CellLayoutTestCaseReader.Board startBoard = - ((CellLayoutTestCaseReader.Board) sections.next()); - CellLayoutTestCaseReader.Arguments point = - ((CellLayoutTestCaseReader.Arguments) sections.next()); - CellLayoutTestCaseReader.Board endBoard = - ((CellLayoutTestCaseReader.Board) sections.next()); - Point moveTo = new Point(Integer.parseInt(point.arguments[0]), - Integer.parseInt(point.arguments[1])); - testCaseMap.put(endBoard.gridSize, - new ReorderTestCase(startBoard.board, moveTo, endBoard.board)); - } - - private Map getTestMap(String testPath) throws IOException { - Map testCaseMap = new HashMap<>(); - Iterator iterableSection = - CellLayoutTestCaseReader.readFromFile(testPath).parse().iterator(); - while (iterableSection.hasNext()) { - addTestCase(iterableSection, testCaseMap); - } - return testCaseMap; - } -} diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt new file mode 100644 index 0000000000..4cecb5a2c7 --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt @@ -0,0 +1,72 @@ +/* + * 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 + +import android.graphics.Point +import android.graphics.Rect +import android.view.View +import android.view.ViewGroup +import com.android.launcher3.CellLayout +import com.android.launcher3.Workspace +import com.android.launcher3.util.CellAndSpan +import com.android.launcher3.widget.LauncherAppWidgetHostView + +object TestUtils { + fun searchChildren(viewGroup: ViewGroup, type: Class): T? where T : View { + for (i in 0.., + cellX: Int, + cellY: Int + ): LauncherAppWidgetHostView { + val view = + (workspace.getPageAt(workspace.currentPage) as CellLayout).getChildAt(cellX, cellY) + assert(view != null) { "There is no view at $cellX , $cellY" } + assert(view is LauncherAppWidgetHostView) { "The view at $cellX , $cellY is not a widget" } + return view as LauncherAppWidgetHostView + } + + fun getCellTopLeftRelativeToCellLayout( + workspace: Workspace<*>, + cellAndSpan: CellAndSpan + ): Point { + val target = Rect() + val cellLayout = workspace.getPageAt(workspace.currentPage) as CellLayout + cellLayout.cellToRect( + cellAndSpan.cellX, + cellAndSpan.cellY, + cellAndSpan.spanX, + cellAndSpan.spanY, + target + ) + return Point(target.left, target.top) + } +} diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt new file mode 100644 index 0000000000..fb61cedaa8 --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt @@ -0,0 +1,64 @@ +/* + * 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.content.Context +import com.android.launcher3.debug.TestEvent +import com.android.launcher3.debug.TestEventEmitter +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Rule to create EventWaiters to wait for events that happens on the Launcher. For reference look + * at [TestEvent] for existing events. + * + * 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. + */ +class EventsRule(val context: Context) : TestRule { + + private var prevEventEmitter: TestEventEmitter? = null + + private val eventEmitter = TestEventsEmitterImplementation() + + override fun apply(base: Statement, description: Description?): Statement { + return object : Statement() { + override fun evaluate() { + beforeTest() + base.evaluate() + afterTest() + } + } + } + + fun createEventWaiter(expectedEvent: TestEvent): EventWaiter { + return eventEmitter.createEventWaiter(expectedEvent) + } + + private fun beforeTest() { + prevEventEmitter = TestEventEmitter.INSTANCE.get(context) + TestEventEmitter.INSTANCE.initializeForTesting(eventEmitter) + } + + private fun afterTest() { + TestEventEmitter.INSTANCE.initializeForTesting(prevEventEmitter) + } +} diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt new file mode 100644 index 0000000000..365ad4b0de --- /dev/null +++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt @@ -0,0 +1,89 @@ +/* + * 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() + + companion object { + private const val TAG = "EventWaiter" + } + + fun waitForSignal(timeout: Long = TimeUnit.SECONDS.toMillis(10)) = 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 = 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() + } +}