Snap for 7947231 from eace1ba76d to sc-v2-release
Change-Id: I85d287543966c4203e006599e26204b88a623cc5
This commit is contained in:
@@ -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
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user