diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java index e82c900734..f9a0bb194b 100644 --- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java +++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -161,8 +161,7 @@ public class QuickstepModelDelegate extends ModelDelegate { } InstanceId instanceId = new InstanceIdSequence().newInstanceId(); for (ItemInfo info : itemsIdMap) { - FolderInfo parent = info.container > 0 - ? (FolderInfo) itemsIdMap.get(info.container) : null; + FolderInfo parent = getContainer(info, itemsIdMap); StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId); } additionalSnapshotEvents(instanceId); @@ -199,8 +198,7 @@ public class QuickstepModelDelegate extends ModelDelegate { } for (ItemInfo info : itemsIdMap) { - FolderInfo parent = info.container > 0 - ? (FolderInfo) itemsIdMap.get(info.container) : null; + FolderInfo parent = getContainer(info, itemsIdMap); LauncherAtom.ItemInfo itemInfo = info.buildProto(parent); Log.d(TAG, itemInfo.toString()); StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo, @@ -222,6 +220,22 @@ public class QuickstepModelDelegate extends ModelDelegate { } } + private static FolderInfo getContainer(ItemInfo info, IntSparseArrayMap itemsIdMap) { + if (info.container > 0) { + ItemInfo containerInfo = itemsIdMap.get(info.container); + + if (!(containerInfo instanceof FolderInfo)) { + Log.e(TAG, String.format( + "Item info: %s found with invalid container: %s", + info, + containerInfo)); + } else { + return (FolderInfo) containerInfo; + } + } + return null; + } + @Override public void validateData() { super.validateData(); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index 56730dbd70..2d4942d058 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -19,6 +19,9 @@ import androidx.annotation.NonNull; import com.android.systemui.shared.rotation.RotationButtonController; +import java.util.ArrayList; +import java.util.List; + /** * Hosts various taskbar controllers to facilitate passing between one another. */ @@ -43,6 +46,9 @@ public class TaskbarControllers { /** Do not store this controller, as it may change at runtime. */ @NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT; + private boolean mAreAllControllersInitialized; + private final List mPostInitCallbacks = new ArrayList<>(); + public TaskbarControllers(TaskbarActivityContext taskbarActivityContext, TaskbarDragController taskbarDragController, TaskbarNavButtonController navButtonController, @@ -81,6 +87,8 @@ public class TaskbarControllers { * in constructors for now, as some controllers may still be waiting for init(). */ public void init(TaskbarSharedState sharedState) { + mAreAllControllersInitialized = false; + taskbarDragController.init(this); navbarButtonsViewController.init(this); rotationButtonController.init(); @@ -92,6 +100,12 @@ public class TaskbarControllers { stashedHandleViewController.init(this); taskbarStashController.init(this, sharedState); taskbarEduController.init(this); + + mAreAllControllersInitialized = true; + for (Runnable postInitCallback : mPostInitCallbacks) { + postInitCallback.run(); + } + mPostInitCallbacks.clear(); } /** @@ -108,4 +122,17 @@ public class TaskbarControllers { stashedHandleViewController.onDestroy(); taskbarAutohideSuspendController.onDestroy(); } + + /** + * If all controllers are already initialized, runs the given callback immediately. Otherwise, + * queues it to run after calling init() on all controllers. This should likely be used in any + * case where one controller is telling another controller to do something inside init(). + */ + public void runAfterInit(Runnable callback) { + if (mAreAllControllersInitialized) { + callback.run(); + } else { + mPostInitCallbacks.add(callback); + } + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java index 6dcfe564a6..37a9b5e5b2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java @@ -184,9 +184,12 @@ public class TaskbarModelCallbacks implements } mContainer.updateHotseatItems(hotseatItemInfos); - mControllers.taskbarStashController.updateStateForFlag( - TaskbarStashController.FLAG_STASHED_IN_APP_EMPTY, isHotseatEmpty); - mControllers.taskbarStashController.applyState(); + final boolean finalIsHotseatEmpty = isHotseatEmpty; + mControllers.runAfterInit(() -> { + mControllers.taskbarStashController.updateStateForFlag( + TaskbarStashController.FLAG_STASHED_IN_APP_EMPTY, finalIsHotseatEmpty); + mControllers.taskbarStashController.applyState(); + }); } @Override diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java index 22f67d2960..d2373c9b90 100644 --- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java +++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java @@ -37,12 +37,12 @@ import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.quickstep.FallbackActivityInterface; import com.android.quickstep.GestureState; import com.android.quickstep.RecentsActivity; +import com.android.quickstep.util.GroupTask; import com.android.quickstep.util.SplitSelectStateController; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; -import com.android.quickstep.util.GroupTask; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.Task.TaskKey; @@ -206,10 +206,6 @@ public class FallbackRecentsView extends RecentsView mLogEntries = new ArrayList<>(); + + protected LoaderMemoryLogger() {} + + protected void addLog(int logLevel, String tag, String log) { + addLog(logLevel, tag, log, null); + } + + protected void addLog( + int logLevel, String tag, String log, Exception stackTrace) { + switch (logLevel) { + case Log.ASSERT: + case Log.ERROR: + case Log.DEBUG: + case Log.INFO: + case Log.VERBOSE: + case Log.WARN: + mLogEntries.add(new LogEntry(logLevel, tag, log, stackTrace)); + break; + default: + throw new IllegalArgumentException("Invalid log level provided: " + logLevel); + + } + } + + protected void clearLogs() { + mLogEntries.clear(); + } + + protected void printLogs() { + for (LogEntry logEntry : mLogEntries) { + String tag = String.format("%s: %s", TAG, logEntry.mLogTag); + String logString = logEntry.mStackStrace == null + ? logEntry.mLogString + : String.format( + "%s\n%s", + logEntry.mLogString, + Log.getStackTraceString(logEntry.mStackStrace)); + + Log.println(logEntry.mLogLevel, tag, logString); + } + clearLogs(); + } + + private static class LogEntry { + + protected final int mLogLevel; + protected final String mLogTag; + protected final String mLogString; + @Nullable protected final Exception mStackStrace; + + protected LogEntry( + int logLevel, String logTag, String logString, @Nullable Exception stackStrace) { + mLogLevel = logLevel; + mLogTag = logTag; + mLogString = logString; + mStackStrace = stackStrace; + } + } +} diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index a4f6f7aa69..2a0f9a6f67 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -52,6 +52,8 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.TimingLogger; +import androidx.annotation.Nullable; + import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; @@ -197,11 +199,12 @@ public class LoaderTask implements Runnable { Object traceToken = TraceHelper.INSTANCE.beginSection(TAG); TimingLogger logger = new TimingLogger(TAG, "run"); + LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger(); try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { List allShortcuts = new ArrayList<>(); Trace.beginSection("LoadWorkspace"); try { - loadWorkspace(allShortcuts); + loadWorkspace(allShortcuts, memoryLogger); } finally { Trace.endSection(); } @@ -311,9 +314,13 @@ public class LoaderTask implements Runnable { mModelDelegate.modelLoadComplete(); transaction.commit(); + memoryLogger.clearLogs(); } catch (CancellationException e) { // Loader stopped, ignore logASplit(logger, "Cancelled"); + } catch (Exception e) { + memoryLogger.printLogs(); + throw e; } finally { logger.dumpToLog(); } @@ -325,13 +332,21 @@ public class LoaderTask implements Runnable { this.notify(); } - private void loadWorkspace(List allDeepShortcuts) { + private void loadWorkspace(List allDeepShortcuts, LoaderMemoryLogger logger) { loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI, - null /* selection */); + null /* selection */, logger); } - protected void loadWorkspace(List allDeepShortcuts, Uri contentUri, - String selection) { + protected void loadWorkspace( + List allDeepShortcuts, Uri contentUri, String selection) { + loadWorkspace(allDeepShortcuts, contentUri, selection, null); + } + + protected void loadWorkspace( + List allDeepShortcuts, + Uri contentUri, + String selection, + @Nullable LoaderMemoryLogger logger) { final Context context = mApp.getContext(); final ContentResolver contentResolver = context.getContentResolver(); final PackageManagerHelper pmHelper = new PackageManagerHelper(context); @@ -635,7 +650,7 @@ public class LoaderTask implements Runnable { } } - c.checkAndAddItem(info, mBgDataModel); + c.checkAndAddItem(info, mBgDataModel, logger); } else { throw new RuntimeException("Unexpected null WorkspaceItemInfo"); } @@ -654,7 +669,7 @@ public class LoaderTask implements Runnable { // no special handling required for restored folders c.markRestored(); - c.checkAndAddItem(folderInfo, mBgDataModel); + c.checkAndAddItem(folderInfo, mBgDataModel, logger); break; case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: diff --git a/tests/Android.bp b/tests/Android.bp index c329eceead..3670c37add 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -23,7 +23,7 @@ package { // Source code used for test filegroup { name: "launcher-tests-src", - srcs: ["src/**/*.java", "src/**/*.kt"], + srcs: ["src/**/*.java"], } // Source code used for oop test helpers diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java new file mode 100644 index 0000000000..8a4590a388 --- /dev/null +++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java @@ -0,0 +1,201 @@ +package com.android.launcher3.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.util.Pair; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.util.ContentWriter; +import com.android.launcher3.util.Executors; +import com.android.launcher3.util.GridOccupancy; +import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.IntSparseArrayMap; +import com.android.launcher3.util.LauncherModelHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link AddWorkspaceItemsTask} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AddWorkspaceItemsTaskTest { + + private final ComponentName mComponent1 = new ComponentName("a", "b"); + private final ComponentName mComponent2 = new ComponentName("b", "b"); + + private Context mTargetContext; + private InvariantDeviceProfile mIdp; + private LauncherAppState mAppState; + private LauncherModelHelper mModelHelper; + + private IntArray mExistingScreens; + private IntArray mNewScreens; + private IntSparseArrayMap mScreenOccupancy; + + @Before + public void setup() { + mModelHelper = new LauncherModelHelper(); + mTargetContext = mModelHelper.sandboxContext; + mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); + mIdp.numColumns = mIdp.numRows = 5; + mAppState = LauncherAppState.getInstance(mTargetContext); + + mExistingScreens = new IntArray(); + mScreenOccupancy = new IntSparseArrayMap<>(); + mNewScreens = new IntArray(); + } + + @After + public void tearDown() { + mModelHelper.destroy(); + } + + private AddWorkspaceItemsTask newTask(ItemInfo... items) { + List> list = new ArrayList<>(); + for (ItemInfo item : items) { + list.add(Pair.create(item, null)); + } + return new AddWorkspaceItemsTask(list); + } + + @Test + public void testFindSpaceForItem_prefers_second() throws Exception { + // First screen has only one hole of size 1 + int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); + + // Second screen has 2 holes of sizes 3x2 and 2x3 + setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5)); + + int[] spaceFound = newTask().findSpaceForItem( + mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1); + assertEquals(1, spaceFound[0]); + assertTrue(mScreenOccupancy.get(spaceFound[0]) + .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1)); + + // Find a larger space + spaceFound = newTask().findSpaceForItem( + mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3); + assertEquals(2, spaceFound[0]); + assertTrue(mScreenOccupancy.get(spaceFound[0]) + .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3)); + } + + @Test + public void testFindSpaceForItem_adds_new_screen() throws Exception { + // First screen has 2 holes of sizes 3x2 and 2x3 + setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5)); + + IntArray oldScreens = mExistingScreens.clone(); + int[] spaceFound = newTask().findSpaceForItem( + mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3); + assertFalse(oldScreens.contains(spaceFound[0])); + assertTrue(mNewScreens.contains(spaceFound[0])); + } + + @Test + public void testAddItem_existing_item_ignored() throws Exception { + WorkspaceItemInfo info = new WorkspaceItemInfo(); + info.intent = new Intent().setComponent(mComponent1); + + // Setup a screen with a hole + setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); + + // Nothing was added + assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty()); + } + + @Test + public void testAddItem_some_items_added() throws Exception { + Callbacks callbacks = mock(Callbacks.class); + Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get(); + + WorkspaceItemInfo info = new WorkspaceItemInfo(); + info.intent = new Intent().setComponent(mComponent1); + + WorkspaceItemInfo info2 = new WorkspaceItemInfo(); + info2.intent = new Intent().setComponent(mComponent2); + + // Setup a screen with a hole + setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); + + mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run(); + ArgumentCaptor notAnimated = ArgumentCaptor.forClass(ArrayList.class); + ArgumentCaptor animated = ArgumentCaptor.forClass(ArrayList.class); + + // only info2 should be added because info was already added to the workspace + // in setupWorkspaceWithHoles() + verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(), + animated.capture()); + assertTrue(notAnimated.getValue().isEmpty()); + + assertEquals(1, animated.getValue().size()); + assertTrue(animated.getValue().contains(info2)); + } + + private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception { + return mModelHelper.executeSimpleTask( + model -> writeWorkspaceWithHoles(model, startId, screenId, holes)); + } + + private int writeWorkspaceWithHoles( + BgDataModel bgDataModel, int startId, int screenId, Rect... holes) { + GridOccupancy occupancy = new GridOccupancy(mIdp.numColumns, mIdp.numRows); + occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true); + for (Rect r : holes) { + occupancy.markCells(r, false); + } + + mExistingScreens.add(screenId); + mScreenOccupancy.append(screenId, occupancy); + + for (int x = 0; x < mIdp.numColumns; x++) { + for (int y = 0; y < mIdp.numRows; y++) { + if (!occupancy.cells[x][y]) { + continue; + } + + WorkspaceItemInfo info = new WorkspaceItemInfo(); + info.intent = new Intent().setComponent(mComponent1); + info.id = startId++; + info.screenId = screenId; + info.cellX = x; + info.cellY = y; + info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + bgDataModel.addItem(mTargetContext, info, false); + + ContentWriter writer = new ContentWriter(mTargetContext); + info.writeToValues(writer); + writer.put(Favorites._ID, info.id); + mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI, + writer.getValues(mTargetContext)); + } + } + return startId; + } +} diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt deleted file mode 100644 index e315658d37..0000000000 --- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright (C) 2021 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.model - -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.graphics.Rect -import android.util.Pair -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.launcher3.InvariantDeviceProfile -import com.android.launcher3.LauncherAppState -import com.android.launcher3.LauncherSettings -import com.android.launcher3.model.data.ItemInfo -import com.android.launcher3.model.data.WorkspaceItemInfo -import com.android.launcher3.util.* -import com.android.launcher3.util.IntArray -import org.junit.After -import org.junit.Assert.* -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.* -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito.verify -import kotlin.collections.ArrayList - -/** - * Tests for [AddWorkspaceItemsTask] - */ -@SmallTest -@RunWith(AndroidJUnit4::class) -class AddWorkspaceItemsTaskTest { - - @Captor - private lateinit var animatedItemArgumentCaptor: ArgumentCaptor> - - @Captor - private lateinit var notAnimatedItemArgumentCaptor: ArgumentCaptor> - - @Mock - private lateinit var dataModelCallbacks: BgDataModel.Callbacks - - private lateinit var mTargetContext: Context - private lateinit var mIdp: InvariantDeviceProfile - private lateinit var mAppState: LauncherAppState - private lateinit var mModelHelper: LauncherModelHelper - private lateinit var mExistingScreens: IntArray - private lateinit var mNewScreens: IntArray - private lateinit var mScreenOccupancy: IntSparseArrayMap - - private val emptyScreenHoles = listOf(Rect(0, 0, 5, 5)) - private val fullScreenHoles = emptyList() - - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - mModelHelper = LauncherModelHelper() - mTargetContext = mModelHelper.sandboxContext - mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext] - mIdp.numRows = 5 - mIdp.numColumns = mIdp.numRows - mAppState = LauncherAppState.getInstance(mTargetContext) - mExistingScreens = IntArray() - mScreenOccupancy = IntSparseArrayMap() - mNewScreens = IntArray() - Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(dataModelCallbacks) }.get() - } - - @After - fun tearDown() { - mModelHelper.destroy() - } - - @Test - fun justEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnFirstScreenId() { - setupWorkspacesWithHoles( - screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole - // 2 holes of sizes 3x2 and 2x3 - screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)), - ) - - val spaceFound = newTask().findSpaceForItem( - mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 1, 1) - assertEquals(1, spaceFound[0]) - assertTrue(mScreenOccupancy[spaceFound[0]] - .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1)) - } - - @Test - fun notEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnSecondScreenId() { - setupWorkspacesWithHoles( - screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole - // 2 holes of sizes 3x2 and 2x3 - screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)), - ) - - // Find a larger space - val spaceFound = newTask().findSpaceForItem( - mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 2, 3) - assertEquals(2, spaceFound[0]) - assertTrue(mScreenOccupancy[spaceFound[0]] - .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3)) - } - - @Test - fun notEnoughSpaceOnExistingScreens_whenFindSpaceForItem_thenReturnNewScreenId() { - setupWorkspacesWithHoles( - // 2 holes of sizes 3x2 and 2x3 - screen1 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)), - // 2 holes of sizes 1x2 and 2x2 - screen2 = listOf(Rect(1, 0, 2, 2), Rect(3, 2, 5, 4)), - ) - - val oldScreens = mExistingScreens.clone() - val spaceFound = newTask().findSpaceForItem( - mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 3, 3) - assertFalse(oldScreens.contains(spaceFound[0])) - assertTrue(mNewScreens.contains(spaceFound[0])) - } - - @Test - fun enoughSpaceOnFirstScreen_whenTaskRuns_thenAddItemToFirstScreen() { - val workspaceHoles = createWorkspaceHoles( - screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space - screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(1, addedItems.first().itemInfo.screenId) - } - - @Test - fun firstPageIsFull_whenTaskRuns_thenAddItemToSecondScreen() { - val workspaceHoles = createWorkspaceHoles( - screen1 = fullScreenHoles, - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(2, addedItems.first().itemInfo.screenId) - } - - @Test - fun firstScreenIsEmptyButSecondIsNotEmpty_whenTaskRuns_thenAddItemToSecondScreen() { - val workspaceHoles = createWorkspaceHoles( - screen1 = emptyScreenHoles, - screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(2, addedItems.first().itemInfo.screenId) - } - - @Test - fun twoEmptyMiddleScreens_whenTaskRuns_thenAddItemToThirdScreen() { - val workspaceHoles = createWorkspaceHoles( - screen1 = emptyScreenHoles, - screen2 = emptyScreenHoles, - screen3 = listOf(Rect(1, 1, 4, 4)), // 3x3 space - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(3, addedItems.first().itemInfo.screenId) - } - - @Test - fun allPagesAreFull_whenTaskRuns_thenAddItemToNewScreen() { - val workspaceHoles = createWorkspaceHoles( - screen1 = fullScreenHoles, - screen2 = fullScreenHoles, - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(3, addedItems.first().itemInfo.screenId) - } - - @Test - fun firstTwoPagesAreFull_and_ThirdPageIsEmpty_whenTaskRuns_thenAddItemToThirdPage() { - val workspaceHoles = createWorkspaceHoles( - screen1 = fullScreenHoles, - screen2 = fullScreenHoles, - screen3 = emptyScreenHoles - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(3, addedItems.first().itemInfo.screenId) - } - - @Test - fun itemIsAlreadyAdded_whenTaskRun_thenIgnoreItem() { - val task = newTask(getExistingItem()) - setupWorkspacesWithHoles( - screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole - ) - - // Nothing was added - assertTrue(mModelHelper.executeTaskForTest(task).isEmpty()) - } - - @Test - fun newAndExistingItems_whenTaskRun_thenAddOnlyTheNewOne() { - val newItem = getNewItem() - val workspaceHoles = createWorkspaceHoles( - screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole - ) - val addedItems = testAddItems(workspaceHoles, getExistingItem(), newItem) - assertEquals(1, addedItems.size) - val addedItem = addedItems.first() - assert(addedItem.isAnimated) - val addedItemInfo = addedItem.itemInfo - assertEquals(1, addedItemInfo.screenId) - assertEquals(newItem, addedItemInfo) - } - - private fun testAddItems( - workspaceHoles: List>, - vararg itemsToAdd: WorkspaceItemInfo - ): List { - setupWorkspaces(workspaceHoles) - mModelHelper.executeTaskForTest(newTask(*itemsToAdd))[0].run() - - verify(dataModelCallbacks).bindAppsAdded(any(), - notAnimatedItemArgumentCaptor.capture(), animatedItemArgumentCaptor.capture()) - - val addedItems = mutableListOf() - addedItems.addAll(animatedItemArgumentCaptor.value.map { AddedItem(it, true) }) - addedItems.addAll(notAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) }) - return addedItems - } - - private fun setupWorkspaces(workspaceHoles: List>) { - var nextItemId = 1 - var screenId = 1 - workspaceHoles.forEach { holes -> - nextItemId = setupWorkspace(nextItemId, screenId++, *holes.toTypedArray()) - } - } - - private fun setupWorkspace(startId: Int, screenId: Int, vararg holes: Rect): Int { - return mModelHelper.executeSimpleTask { dataModel -> - writeWorkspaceWithHoles(dataModel, startId, screenId, *holes) - } - } - - private fun writeWorkspaceWithHoles( - bgDataModel: BgDataModel, - itemStartId: Int, - screenId: Int, - vararg holes: Rect, - ): Int { - var itemId = itemStartId - val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows) - occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true) - holes.forEach { holeRect -> - occupancy.markCells(holeRect, false) - } - mExistingScreens.add(screenId) - mScreenOccupancy.append(screenId, occupancy) - for (x in 0 until mIdp.numColumns) { - for (y in 0 until mIdp.numRows) { - if (!occupancy.cells[x][y]) { - continue - } - val info = getExistingItem() - info.id = itemId++ - info.screenId = screenId - info.cellX = x - info.cellY = y - info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP - bgDataModel.addItem(mTargetContext, info, false) - val writer = ContentWriter(mTargetContext) - info.writeToValues(writer) - writer.put(LauncherSettings.Favorites._ID, info.id) - mTargetContext.contentResolver.insert(LauncherSettings.Favorites.CONTENT_URI, - writer.getValues(mTargetContext)) - } - } - return itemId - } - - private fun setupWorkspacesWithHoles( - screen1: List? = null, - screen2: List? = null, - screen3: List? = null, - ) = createWorkspaceHoles(screen1, screen2, screen3) - .let(this::setupWorkspaces) - - private fun createWorkspaceHoles( - screen1: List? = null, - screen2: List? = null, - screen3: List? = null, - ): List> = listOfNotNull(screen1, screen2, screen3) - - private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask = - items.map { Pair.create(it, Any()) } - .toMutableList() - .let(::AddWorkspaceItemsTask) - - private fun getExistingItem() = WorkspaceItemInfo() - .apply { intent = Intent().setComponent(ComponentName("a", "b")) } - - private fun getNewItem() = WorkspaceItemInfo() - .apply { intent = Intent().setComponent(ComponentName("b", "b")) } -} - -private data class AddedItem( - val itemInfo: ItemInfo, - val isAnimated: Boolean -) \ No newline at end of file diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java index d5479fbd9e..3eb8cf1096 100644 --- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java +++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java @@ -212,7 +212,7 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to get overview actions")) { verifyActiveContainer(); - UiObject2 overviewActions = mLauncher.waitForLauncherObject("action_buttons"); + UiObject2 overviewActions = mLauncher.waitForOverviewObject("action_buttons"); return new OverviewActions(overviewActions, mLauncher); } } @@ -224,19 +224,16 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { return mLauncher.hasLauncherObject(mLauncher.getOverviewObjectSelector("clear_all")); } - /* TODO(b/197630182): Once b/188790554 is fixed, remove instanceof check. Currently, when - swiping from app to overview in Fallback Recents, taskbar remains and no action buttons - are visible, so we are only testing Overview for now, not BaseOverview. */ private void verifyActionsViewVisibility() { - if (!(this instanceof Overview) || !hasTasks()) { + if (!hasTasks()) { return; } try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to assert overview actions view visibility")) { if (mLauncher.isTablet() && !isOverviewSnappedToFocusedTaskForTablet()) { - mLauncher.waitUntilLauncherObjectGone("action_buttons"); + mLauncher.waitUntilOverviewObjectGone("action_buttons"); } else { - mLauncher.waitForLauncherObject("action_buttons"); + mLauncher.waitForOverviewObject("action_buttons"); } } } diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 3485dd159e..91b1bc70c2 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -1029,6 +1029,10 @@ public final class LauncherInstrumentation { waitUntilGoneBySelector(getLauncherObjectSelector(resId)); } + void waitUntilOverviewObjectGone(String resId) { + waitUntilGoneBySelector(getOverviewObjectSelector(resId)); + } + void waitUntilLauncherObjectGone(BySelector selector) { waitUntilGoneBySelector(makeLauncherSelector(selector)); }