1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.celllayout; 17 18 import static android.platform.uiautomator_helpers.DeviceHelpers.getContext; 19 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertTrue; 22 23 import android.content.ContentResolver; 24 import android.content.ContentValues; 25 import android.graphics.Point; 26 import android.net.Uri; 27 import android.util.Log; 28 29 import androidx.test.ext.junit.runners.AndroidJUnit4; 30 import androidx.test.filters.SmallTest; 31 32 import com.android.launcher3.InvariantDeviceProfile; 33 import com.android.launcher3.Launcher; 34 import com.android.launcher3.LauncherAppState; 35 import com.android.launcher3.MultipageCellLayout; 36 import com.android.launcher3.celllayout.board.CellLayoutBoard; 37 import com.android.launcher3.celllayout.board.TestWorkspaceBuilder; 38 import com.android.launcher3.celllayout.board.WidgetRect; 39 import com.android.launcher3.tapl.Widget; 40 import com.android.launcher3.tapl.WidgetResizeFrame; 41 import com.android.launcher3.ui.AbstractLauncherUiTest; 42 import com.android.launcher3.util.ModelTestExtensions; 43 import com.android.launcher3.util.rule.ShellCommandRule; 44 45 import org.junit.After; 46 import org.junit.Assert; 47 import org.junit.Assume; 48 import org.junit.Before; 49 import org.junit.Rule; 50 import org.junit.Test; 51 import org.junit.runner.RunWith; 52 53 import java.io.IOException; 54 import java.util.ArrayList; 55 import java.util.HashMap; 56 import java.util.Iterator; 57 import java.util.List; 58 import java.util.Map; 59 60 @SmallTest 61 @RunWith(AndroidJUnit4.class) 62 public class TaplReorderWidgetsTest extends AbstractLauncherUiTest<Launcher> { 63 64 @Rule 65 public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); 66 67 private static final String TAG = TaplReorderWidgetsTest.class.getSimpleName(); 68 69 private static final List<String> FOLDABLE_GRIDS = List.of("normal", "practical", "reasonable"); 70 71 TestWorkspaceBuilder mWorkspaceBuilder; 72 73 @Before setup()74 public void setup() throws Throwable { 75 mWorkspaceBuilder = new TestWorkspaceBuilder(mTargetContext); 76 super.setUp(); 77 } 78 79 @After tearDown()80 public void tearDown() { 81 ModelTestExtensions.INSTANCE.clearModelDb( 82 LauncherAppState.getInstance(getContext()).getModel() 83 ); 84 } 85 86 /** 87 * Validate if the given board represent the current CellLayout 88 **/ validateBoard(List<CellLayoutBoard> testBoards)89 private boolean validateBoard(List<CellLayoutBoard> testBoards) { 90 ArrayList<CellLayoutBoard> workspaceBoards = workspaceToBoards(); 91 if (workspaceBoards.size() < testBoards.size()) { 92 return false; 93 } 94 for (int i = 0; i < testBoards.size(); i++) { 95 if (testBoards.get(i).compareTo(workspaceBoards.get(i)) != 0) { 96 return false; 97 } 98 } 99 return true; 100 } 101 buildWorkspaceFromBoards(List<CellLayoutBoard> boards, FavoriteItemsTransaction transaction)102 private FavoriteItemsTransaction buildWorkspaceFromBoards(List<CellLayoutBoard> boards, 103 FavoriteItemsTransaction transaction) { 104 for (int i = 0; i < boards.size(); i++) { 105 CellLayoutBoard board = boards.get(i); 106 mWorkspaceBuilder.buildFromBoard(board, transaction, i); 107 } 108 return transaction; 109 } 110 printCurrentWorkspace()111 private void printCurrentWorkspace() { 112 InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); 113 ArrayList<CellLayoutBoard> boards = workspaceToBoards(); 114 for (int i = 0; i < boards.size(); i++) { 115 Log.d(TAG, "Screen number " + i); 116 Log.d(TAG, ".\n" + boards.get(i).toString(idp.numColumns, idp.numRows)); 117 } 118 } 119 workspaceToBoards()120 private ArrayList<CellLayoutBoard> workspaceToBoards() { 121 return getFromLauncher(CellLayoutTestUtils::workspaceToBoards); 122 } 123 getWidgetClosestTo(Point point)124 private WidgetRect getWidgetClosestTo(Point point) { 125 ArrayList<CellLayoutBoard> workspaceBoards = workspaceToBoards(); 126 int maxDistance = 9999; 127 WidgetRect bestRect = null; 128 for (int i = 0; i < workspaceBoards.get(0).getWidgets().size(); i++) { 129 WidgetRect widget = workspaceBoards.get(0).getWidgets().get(i); 130 if (widget.getCellX() == 0 && widget.getCellY() == 0) { 131 continue; 132 } 133 int distance = Math.abs(point.x - widget.getCellX()) 134 + Math.abs(point.y - widget.getCellY()); 135 if (distance == 0) { 136 break; 137 } 138 if (distance < maxDistance) { 139 maxDistance = distance; 140 bestRect = widget; 141 } 142 } 143 return bestRect; 144 } 145 146 /** 147 * This function might be odd, its function is to select a widget and leave it in its place. 148 * The idea is to make the test broader and also test after a widgets resized because the 149 * underlying code does different things in that case 150 */ triggerWidgetResize(ReorderTestCase testCase)151 private void triggerWidgetResize(ReorderTestCase testCase) { 152 WidgetRect widgetRect = getWidgetClosestTo(testCase.moveMainTo); 153 if (widgetRect == null) { 154 // Some test doesn't have a widget in the final position, in those cases we will ignore 155 // them 156 return; 157 } 158 Widget widget = mLauncher.getWorkspace().getWidgetAtCell(widgetRect.getCellX(), 159 widgetRect.getCellY()); 160 WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(widgetRect.getCellX(), 161 widgetRect.getCellY(), widgetRect.getSpanX(), widgetRect.getSpanY()); 162 resizeFrame.dismiss(); 163 } 164 runTestCase(ReorderTestCase testCase)165 private void runTestCase(ReorderTestCase testCase) { 166 WidgetRect mainWidgetCellPos = CellLayoutBoard.getMainFromList( 167 testCase.mStart); 168 169 FavoriteItemsTransaction transaction = 170 new FavoriteItemsTransaction(mTargetContext); 171 transaction = buildWorkspaceFromBoards(testCase.mStart, transaction); 172 transaction.commit(); 173 mLauncher.waitForLauncherInitialized(); 174 // resetLoaderState triggers the launcher to start loading the workspace which allows 175 // waitForLauncherCondition to wait for that condition, otherwise the condition would 176 // always be true and it wouldn't wait for the changes to be applied. 177 waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading()); 178 179 triggerWidgetResize(testCase); 180 181 Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.getCellX(), 182 mainWidgetCellPos.getCellY()); 183 assertNotNull(widget); 184 WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(testCase.moveMainTo.x, 185 testCase.moveMainTo.y, mainWidgetCellPos.getSpanX(), mainWidgetCellPos.getSpanY()); 186 resizeFrame.dismiss(); 187 188 boolean isValid = false; 189 for (List<CellLayoutBoard> boards : testCase.mEnd) { 190 isValid |= validateBoard(boards); 191 if (isValid) break; 192 } 193 printCurrentWorkspace(); 194 assertTrue("Non of the valid boards match with the current state", isValid); 195 } 196 197 /** 198 * Run only the test define for the current grid size if such test exist 199 * 200 * @param testCaseMap map containing all the tests per grid size (Point) 201 */ runTestCaseMap(Map<Point, ReorderTestCase> testCaseMap, String testName)202 private boolean runTestCaseMap(Map<Point, ReorderTestCase> testCaseMap, String testName) { 203 Point iconGridDimensions = mLauncher.getWorkspace().getIconGridDimensions(); 204 Log.d(TAG, "Running test " + testName + " for grid " + iconGridDimensions); 205 if (!testCaseMap.containsKey(iconGridDimensions)) { 206 Log.d(TAG, "The test " + testName + " doesn't support " + iconGridDimensions 207 + " grid layout"); 208 return false; 209 } 210 runTestCase(testCaseMap.get(iconGridDimensions)); 211 212 return true; 213 } 214 runTestCaseMapForAllGrids(Map<Point, ReorderTestCase> testCaseMap, String testName)215 private void runTestCaseMapForAllGrids(Map<Point, ReorderTestCase> testCaseMap, 216 String testName) { 217 boolean runAtLeastOnce = false; 218 for (String grid : FOLDABLE_GRIDS) { 219 applyGridOption(grid); 220 mLauncher.waitForLauncherInitialized(); 221 runAtLeastOnce |= runTestCaseMap(testCaseMap, testName); 222 } 223 Assume.assumeTrue("None of the grids are supported", runAtLeastOnce); 224 } 225 applyGridOption(Object argValue)226 private void applyGridOption(Object argValue) { 227 String testProviderAuthority = mTargetContext.getPackageName() + ".grid_control"; 228 Uri gridUri = new Uri.Builder() 229 .scheme(ContentResolver.SCHEME_CONTENT) 230 .authority(testProviderAuthority) 231 .appendPath("default_grid") 232 .build(); 233 ContentValues values = new ContentValues(); 234 values.putObject("name", argValue); 235 Assert.assertEquals(1, 236 mTargetContext.getContentResolver().update(gridUri, values, null, null)); 237 } 238 239 @Test simpleReorder()240 public void simpleReorder() throws Exception { 241 runTestCaseMap(getTestMap("ReorderWidgets/simple_reorder_case"), 242 "push_reorder_case"); 243 } 244 245 @Test pushTest()246 public void pushTest() throws Exception { 247 runTestCaseMap(getTestMap("ReorderWidgets/push_reorder_case"), 248 "push_reorder_case"); 249 } 250 251 @Test fullReorder()252 public void fullReorder() throws Exception { 253 runTestCaseMap(getTestMap("ReorderWidgets/full_reorder_case"), 254 "full_reorder_case"); 255 } 256 257 @Test moveOutReorder()258 public void moveOutReorder() throws Exception { 259 runTestCaseMap(getTestMap("ReorderWidgets/move_out_reorder_case"), 260 "move_out_reorder_case"); 261 } 262 263 @Test multipleCellLayoutsSimpleReorder()264 public void multipleCellLayoutsSimpleReorder() throws Exception { 265 Assume.assumeTrue("Test doesn't support foldables", getFromLauncher( 266 l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout)); 267 runTestCaseMapForAllGrids(getTestMap("ReorderWidgets/multiple_cell_layouts_simple_reorder"), 268 "multiple_cell_layouts_simple_reorder"); 269 } 270 271 @Test multipleCellLayoutsNoSpaceReorder()272 public void multipleCellLayoutsNoSpaceReorder() throws Exception { 273 Assume.assumeTrue("Test doesn't support foldables", getFromLauncher( 274 l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout)); 275 runTestCaseMapForAllGrids( 276 getTestMap("ReorderWidgets/multiple_cell_layouts_no_space_reorder"), 277 "multiple_cell_layouts_no_space_reorder"); 278 } 279 280 @Test multipleCellLayoutsReorderToOtherSide()281 public void multipleCellLayoutsReorderToOtherSide() throws Exception { 282 Assume.assumeTrue("Test doesn't support foldables", getFromLauncher( 283 l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout)); 284 runTestCaseMapForAllGrids( 285 getTestMap("ReorderWidgets/multiple_cell_layouts_reorder_other_side"), 286 "multiple_cell_layouts_reorder_other_side"); 287 } 288 addTestCase(Iterator<CellLayoutTestCaseReader.TestSection> sections, Map<Point, ReorderTestCase> testCaseMap)289 private void addTestCase(Iterator<CellLayoutTestCaseReader.TestSection> sections, 290 Map<Point, ReorderTestCase> testCaseMap) { 291 CellLayoutTestCaseReader.Board startBoard = 292 ((CellLayoutTestCaseReader.Board) sections.next()); 293 CellLayoutTestCaseReader.Arguments point = 294 ((CellLayoutTestCaseReader.Arguments) sections.next()); 295 CellLayoutTestCaseReader.Board endBoard = 296 ((CellLayoutTestCaseReader.Board) sections.next()); 297 Point moveTo = new Point(Integer.parseInt(point.arguments[0]), 298 Integer.parseInt(point.arguments[1])); 299 testCaseMap.put(endBoard.gridSize, 300 new ReorderTestCase(startBoard.board, moveTo, endBoard.board)); 301 } 302 getTestMap(String testPath)303 private Map<Point, ReorderTestCase> getTestMap(String testPath) throws IOException { 304 Map<Point, ReorderTestCase> testCaseMap = new HashMap<>(); 305 Iterator<CellLayoutTestCaseReader.TestSection> iterableSection = 306 CellLayoutTestCaseReader.readFromFile(testPath).parse().iterator(); 307 while (iterableSection.hasNext()) { 308 addTestCase(iterableSection, testCaseMap); 309 } 310 return testCaseMap; 311 } 312 } 313