Snap for 7947231 from eace1ba76d to sc-v2-release

Change-Id: I85d287543966c4203e006599e26204b88a623cc5
This commit is contained in:
Android Build Coastguard Worker
2021-11-26 00:09:16 +00:00
15 changed files with 407 additions and 359 deletions
@@ -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<ItemInfo> 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();
@@ -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<Runnable> 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);
}
}
}
@@ -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
@@ -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<RecentsActivity, RecentsSta
@Override
public void onStateTransitionStart(RecentsState toState) {
if (toState == HOME) {
// Clean-up logic that occurs when recents is no longer in use/visible.
reset();
}
setOverviewStateEnabled(true);
setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.isFullScreen());
@@ -218,6 +214,10 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsSta
@Override
public void onStateTransitionComplete(RecentsState finalState) {
if (finalState == HOME) {
// Clean-up logic that occurs when recents is no longer in use/visible.
reset();
}
boolean isOverlayEnabled = finalState == DEFAULT || finalState == MODAL_TASK;
setOverlayEnabled(isOverlayEnabled);
setFreezeViewVisibility(false);
@@ -92,10 +92,6 @@ public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher, Laun
@Override
public void onStateTransitionStart(LauncherState toState) {
if (toState == NORMAL || toState == SPRING_LOADED) {
// Clean-up logic that occurs when recents is no longer in use/visible.
reset();
}
setOverviewStateEnabled(toState.overviewUi);
setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
@@ -104,6 +100,10 @@ public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher, Laun
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState == NORMAL || finalState == SPRING_LOADED) {
// Clean-up logic that occurs when recents is no longer in use/visible.
reset();
}
boolean isOverlayEnabled = finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK;
setOverlayEnabled(isOverlayEnabled);
setFreezeViewVisibility(false);
@@ -3185,6 +3185,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
}
}
pageBeginTransition();
setCurrentPage(pageToSnapTo);
// Update various scroll-dependent UI.
dispatchScrollChanged();
@@ -4024,7 +4025,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
/** TODO(b/181707736) More gracefully handle exiting split selection state */
private void resetFromSplitSelectionState() {
if (!showAsGrid()) {
if (!mActivity.getDeviceProfile().overviewShowAsGrid) {
int pageToSnapTo = mCurrentPage;
if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
pageToSnapTo += 1;
@@ -31,6 +31,8 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Workspace;
@@ -215,6 +217,19 @@ public class BgDataModel {
}
public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
addItem(context, item, newItem, null);
}
public synchronized void addItem(
Context context, ItemInfo item, boolean newItem, @Nullable LoaderMemoryLogger logger) {
if (logger != null) {
logger.addLog(
Log.DEBUG,
TAG,
String.format("Adding item to ID map: %s", item.toString()),
/* stackTrace= */ null);
}
itemsIdMap.put(item.id, item);
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
@@ -383,18 +383,23 @@ public class LoaderCursor extends CursorWrapper {
info.cellY = getInt(cellYIndex);
}
public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
checkAndAddItem(info, dataModel, null);
}
/**
* Adds the {@param info} to {@param dataModel} if it does not overlap with any other item,
* otherwise marks it for deletion.
*/
public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
public void checkAndAddItem(
ItemInfo info, BgDataModel dataModel, LoaderMemoryLogger logger) {
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
// Ensure that it is a valid intent. An exception here will
// cause the item loading to get skipped
ShortcutKey.fromItemInfo(info);
}
if (checkItemPlacement(info)) {
dataModel.addItem(mContext, info, false);
dataModel.addItem(mContext, info, false, logger);
} else {
markDeleted("Item position overlap");
}
@@ -0,0 +1,91 @@
/*
* 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.util.Log;
import androidx.annotation.Nullable;
import java.util.ArrayList;
/**
* Helper logger that collects logs while {@code LoaderTask#run} executes and prints them all iff
* an exception is caught in {@code LoaderTask#run}.
*/
public class LoaderMemoryLogger {
private static final String TAG = "LoaderMemoryLogger";
private final ArrayList<LogEntry> 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;
}
}
}
@@ -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<ShortcutInfo> 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<ShortcutInfo> allDeepShortcuts) {
private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, LoaderMemoryLogger logger) {
loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI,
null /* selection */);
null /* selection */, logger);
}
protected void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, Uri contentUri,
String selection) {
protected void loadWorkspace(
List<ShortcutInfo> allDeepShortcuts, Uri contentUri, String selection) {
loadWorkspace(allDeepShortcuts, contentUri, selection, null);
}
protected void loadWorkspace(
List<ShortcutInfo> 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:
+1 -1
View File
@@ -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
@@ -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<GridOccupancy> 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<Pair<ItemInfo, Object>> 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<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
ArgumentCaptor<ArrayList> 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;
}
}
@@ -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<ArrayList<ItemInfo>>
@Captor
private lateinit var notAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
@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<GridOccupancy>
private val emptyScreenHoles = listOf(Rect(0, 0, 5, 5))
private val fullScreenHoles = emptyList<Rect>()
@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<List<Rect>>,
vararg itemsToAdd: WorkspaceItemInfo
): List<AddedItem> {
setupWorkspaces(workspaceHoles)
mModelHelper.executeTaskForTest(newTask(*itemsToAdd))[0].run()
verify(dataModelCallbacks).bindAppsAdded(any(),
notAnimatedItemArgumentCaptor.capture(), animatedItemArgumentCaptor.capture())
val addedItems = mutableListOf<AddedItem>()
addedItems.addAll(animatedItemArgumentCaptor.value.map { AddedItem(it, true) })
addedItems.addAll(notAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
return addedItems
}
private fun setupWorkspaces(workspaceHoles: List<List<Rect>>) {
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<Rect>? = null,
screen2: List<Rect>? = null,
screen3: List<Rect>? = null,
) = createWorkspaceHoles(screen1, screen2, screen3)
.let(this::setupWorkspaces)
private fun createWorkspaceHoles(
screen1: List<Rect>? = null,
screen2: List<Rect>? = null,
screen3: List<Rect>? = null,
): List<List<Rect>> = 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
)
@@ -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");
}
}
}
@@ -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));
}