Migrate model tests to multivalent

Also migrating some other tests that have been verified

Test: SysUI studio
Test: atest NexusLauncherRoboTests
Flag: TEST_ONLY
Change-Id: I072201b8e7b98945ca88657fe813fef4727f16e4
diff --git a/tests/Android.bp b/tests/Android.bp
index c99f656..9945570 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -160,7 +160,7 @@
 }
 
 filegroup {
-    name: "launcher-testing-helpers",
+    name: "launcher-testing-helpers-multivalent",
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
@@ -174,11 +174,20 @@
         // Test classes
         "src/**/*Test.java",
         "src/**/*Test.kt",
+        "src/**/RoboApiWrapper.kt",
         "multivalentTests/src/**/*Test.java",
         "multivalentTests/src/**/*Test.kt",
     ],
 }
 
+filegroup {
+    name: "launcher-testing-helpers",
+    srcs: [
+        ":launcher-testing-helpers-multivalent",
+        "src/**/RoboApiWrapper.kt",
+    ],
+}
+
 android_robolectric_test {
     enabled: true,
     name: "Launcher3RoboTests",
@@ -186,7 +195,7 @@
         ":launcher3-robo-src",
 
         // Test util classes
-        ":launcher-testing-helpers",
+        ":launcher-testing-helpers-multivalent",
         ":launcher-testing-shared",
     ],
     exclude_srcs: [
diff --git a/tests/src/com/android/launcher3/folder/FolderNameInfosTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameInfosTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/folder/FolderNameInfosTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/folder/FolderNameInfosTest.kt
diff --git a/tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/FolderPagedViewTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/folder/FolderPagedViewTest.kt
diff --git a/tests/src/com/android/launcher3/folder/FolderTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/folder/FolderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
diff --git a/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewBackgroundTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java
rename to tests/multivalentTests/src/com/android/launcher3/folder/PreviewBackgroundTest.java
index 7242e9c..b9b7d6a 100644
--- a/tests/src/com/android/launcher3/folder/PreviewBackgroundTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewBackgroundTest.java
@@ -24,15 +24,15 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.PathInterpolator;
 
+import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.launcher3.CellLayout;
+import com.android.launcher3.util.LauncherMultivalentJUnit;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -41,8 +41,8 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@UiThreadTest
+@RunWith(LauncherMultivalentJUnit.class)
 public class PreviewBackgroundTest {
 
     private static final float REST_SCALE = 1f;
diff --git a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
similarity index 98%
rename from tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 7c9f99a..d236551 100644
--- a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -29,6 +29,7 @@
 import com.android.launcher3.icons.BaseIconFactory
 import com.android.launcher3.icons.FastBitmapDrawable
 import com.android.launcher3.icons.UserBadgeDrawable
+import com.android.launcher3.model.ModelTestRule
 import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
@@ -44,6 +45,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -52,6 +54,8 @@
 @RunWith(AndroidJUnit4::class)
 class PreviewItemManagerTest {
 
+    @get:Rule val modelTestRule = ModelTestRule()
+
     private lateinit var previewItemManager: PreviewItemManager
     private lateinit var context: Context
     private lateinit var folderItems: ArrayList<ItemInfo>
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
similarity index 98%
rename from tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
index 370af0c..43dc36b 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -27,6 +27,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.times
@@ -43,6 +44,8 @@
 @RunWith(AndroidJUnit4::class)
 class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
 
+    @get:Rule val modelTestRule = ModelTestRule()
+
     private lateinit var mDataModelCallbacks: MyCallbacks
 
     private val mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder = mock()
diff --git a/tests/src/com/android/launcher3/model/AsyncBindingTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt
similarity index 99%
rename from tests/src/com/android/launcher3/model/AsyncBindingTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt
index af367a8..dce75b9 100644
--- a/tests/src/com/android/launcher3/model/AsyncBindingTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/AsyncBindingTest.kt
@@ -64,6 +64,8 @@
 
     @get:Rule val setFlagsRule = SetFlagsRule()
 
+    @get:Rule val modelTestRule = ModelTestRule()
+
     @Spy private var callbacks = MyCallbacks()
     @Mock private lateinit var itemInflater: ItemInflater<*>
 
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
similarity index 84%
rename from tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
rename to tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 328558d..535080a 100644
--- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.android.launcher3.model;
 
 import static android.os.Process.myUserHandle;
@@ -11,10 +26,13 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.content.pm.PackageInstaller;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -49,6 +67,9 @@
     @Rule(order = 0)
     public TestRule testStabilityRule = new TestStabilityRule();
 
+    @Rule(order = 1)
+    public ModelTestRule mModelTestRule = new ModelTestRule();
+
     private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
     private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
 
@@ -128,10 +149,13 @@
     @Test
     public void testSessionUpdate_updates_pending_apps() {
         // Run on model executor so that no other task runs in the middle.
+        PackageInstaller.SessionInfo sessionInfo = ApplicationProvider.getApplicationContext()
+                        .getPackageManager().getPackageInstaller().getSessionInfo(mSession1);
+        assertNotNull(sessionInfo);
         runOnExecutorSync(MODEL_EXECUTOR, () -> {
             LauncherAppState.getInstance(mContext).getIconCache().updateSessionCache(
                     new PackageUserKey(PENDING_APP_1, myUserHandle()),
-                    mContext.getPackageManager().getPackageInstaller().getSessionInfo(mSession1));
+                    sessionInfo);
 
             // Clear all icons from apps list so that its easy to check what was updated
             allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
diff --git a/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
similarity index 93%
rename from tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
rename to tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 10785f7..e14e145 100644
--- a/tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -22,11 +22,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
-import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -39,6 +39,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -49,8 +50,10 @@
 @RunWith(AndroidJUnit4.class)
 public class DefaultLayoutProviderTest {
 
+    @Rule public ModelTestRule rule = new ModelTestRule();
+
     private LauncherModelHelper mModelHelper;
-    private Context mTargetContext;
+    private LauncherModelHelper.SandboxModelContext mTargetContext;
 
     @Before
     public void setUp() {
@@ -114,8 +117,10 @@
         SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
         params.setAppPackageName(pendingAppPkg);
         params.setAppIcon(BitmapInfo.LOW_RES_ICON);
+        params.installerPackageName = ApplicationProvider.getApplicationContext().getPackageName();
 
-        PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
+        PackageInstaller installer = ApplicationProvider.getApplicationContext().getPackageManager()
+                .getPackageInstaller();
         installer.createSession(params);
 
         writeLayoutAndLoad(new LauncherLayoutBuilder().atWorkspace(0, 1, 0)
diff --git a/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
similarity index 98%
rename from tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
index aadf72e..d2d9512 100644
--- a/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
@@ -21,6 +21,7 @@
 import android.content.Intent
 import android.content.pm.PackageInstaller.SessionInfo
 import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
@@ -33,14 +34,20 @@
 import com.android.launcher3.util.PackageManagerHelper
 import com.android.launcher3.util.PackageUserKey
 import junit.framework.Assert.assertEquals
+import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
+@RunWith(AndroidJUnit4::class)
 class FirstScreenBroadcastHelperTest {
+
+    @get:Rule val modelTestRule = ModelTestRule()
+
     private val context = spy(InstrumentationRegistry.getInstrumentation().targetContext)
     private val mockPmHelper = mock<PackageManagerHelper>()
     private val expectedAppPackage = "appPackageExpected"
diff --git a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
similarity index 96%
rename from tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index c4a4c9b..d002493 100644
--- a/tests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -23,12 +23,14 @@
 import com.android.launcher3.util.LauncherLayoutBuilder
 import com.android.launcher3.util.LauncherModelHelper
 import com.android.launcher3.util.LauncherModelHelper.*
+import com.android.launcher3.util.RoboApiWrapper
 import com.android.launcher3.util.TestUtil
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import java.util.concurrent.CountDownLatch
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -36,6 +38,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class FolderIconLoadTest {
+
+    @get:Rule(order = 0) val modelTestRule = ModelTestRule()
+
     private lateinit var modelHelper: LauncherModelHelper
 
     private val uniqueActivities =
@@ -145,6 +150,7 @@
         while (cache.isIconUpdateInProgress) {
             val wait = CountDownLatch(1)
             Executors.MODEL_EXECUTOR.handler.postDelayed({ wait.countDown() }, 10)
+            RoboApiWrapper.waitForLooperSync(Executors.MODEL_EXECUTOR.handler.looper)
             wait.await()
         }
         TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { cache.clearMemoryCache() }
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/model/LoaderCursorTest.java
rename to tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
index b4945d7..ac911b3 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -67,6 +67,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -77,6 +78,8 @@
 @RunWith(AndroidJUnit4.class)
 public class LoaderCursorTest {
 
+    @Rule public ModelTestRule rule = new ModelTestRule();
+
     private LauncherModelHelper mModelHelper;
     private LauncherAppState mApp;
     private PackageManagerHelper mPmHelper;
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/ModelTestRule.kt b/tests/multivalentTests/src/com/android/launcher3/model/ModelTestRule.kt
new file mode 100644
index 0000000..ad2c2a4
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/model/ModelTestRule.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import com.android.launcher3.util.RoboApiWrapper
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+class ModelTestRule : TestWatcher() {
+    override fun starting(description: Description?) {
+        RoboApiWrapper.initialize()
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
similarity index 87%
rename from tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
rename to tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
index 4ba61ac..a0d9da9 100644
--- a/tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.android.launcher3.model;
 
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -22,6 +37,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -32,6 +48,8 @@
 @RunWith(AndroidJUnit4.class)
 public class PackageInstallStateChangedTaskTest {
 
+    @Rule public ModelTestRule mModelTestRule = new ModelTestRule();
+
     private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
     private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
 
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
similarity index 81%
rename from tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index 6cf3b19..1d9c161 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -25,10 +25,9 @@
 import android.content.pm.ShortcutInfo
 import android.os.Process
 import android.os.UserHandle
-import android.platform.test.annotations.EnableFlags
 import android.util.LongSparseArray
-import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherSettings.Favorites
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
@@ -36,7 +35,6 @@
 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
-import com.android.launcher3.Utilities
 import com.android.launcher3.Utilities.EMPTY_PERSON_ARRAY
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_INFO
@@ -46,7 +44,6 @@
 import com.android.launcher3.model.data.IconRequestInfo
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.LauncherAppWidgetInfo
-import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
 import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.pm.UserCache
@@ -57,11 +54,12 @@
 import com.android.launcher3.util.UserIconInfo
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
 import com.android.launcher3.widget.WidgetInflater
-import com.android.launcher3.widget.WidgetSections
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.RETURNS_DEEP_STUBS
@@ -74,10 +72,12 @@
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
-import org.mockito.quality.Strictness
 
+@RunWith(AndroidJUnit4::class)
 class WorkspaceItemProcessorTest {
 
+    @get:Rule val modelTestRule = ModelTestRule()
+
     @Mock private lateinit var mockIconRequestInfo: IconRequestInfo<WorkspaceItemInfo>
     @Mock private lateinit var mockWorkspaceInfo: WorkspaceItemInfo
     @Mock private lateinit var mockBgDataModel: BgDataModel
@@ -122,6 +122,7 @@
             mock<Context>().apply {
                 whenever(packageManager).thenReturn(mock())
                 whenever(packageManager.getUserBadgedLabel(any(), any())).thenReturn("")
+                whenever(applicationContext).thenReturn(ApplicationProvider.getApplicationContext())
             }
         mockAppState =
             mock<LauncherAppState>().apply {
@@ -666,142 +667,6 @@
     }
 
     @Test
-    fun `When Pending App Widget has not started restore then update db and add item`() {
-
-        val mockitoSession =
-            ExtendedMockito.mockitoSession()
-                .strictness(Strictness.LENIENT)
-                .mockStatic(WidgetSections::class.java)
-                .startMocking()
-        try {
-            // Given
-            val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
-            val expectedComponentName =
-                ComponentName.unflattenFromString(expectedProvider)!!.flattenToString()
-            val expectedRestoreStatus = FLAG_UI_NOT_READY or FLAG_RESTORE_STARTED
-            val expectedAppWidgetId = 0
-            mockCursor.apply {
-                itemType = ITEM_TYPE_APPWIDGET
-                user = mUserHandle
-                restoreFlag = FLAG_UI_NOT_READY
-                container = CONTAINER_DESKTOP
-                whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
-                whenever(appWidgetProvider).thenReturn(expectedProvider)
-                whenever(appWidgetId).thenReturn(expectedAppWidgetId)
-                whenever(spanX).thenReturn(2)
-                whenever(spanY).thenReturn(1)
-                whenever(options).thenReturn(0)
-                whenever(appWidgetSource).thenReturn(20)
-                whenever(applyCommonProperties(any())).thenCallRealMethod()
-                whenever(
-                        updater()
-                            .put(Favorites.APPWIDGET_PROVIDER, expectedComponentName)
-                            .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
-                            .put(Favorites.RESTORED, expectedRestoreStatus)
-                            .commit()
-                    )
-                    .thenReturn(1)
-            }
-            val inflationResult =
-                WidgetInflater.InflationResult(
-                    type = WidgetInflater.TYPE_PENDING,
-                    widgetInfo = null
-                )
-            mockWidgetInflater =
-                mock<WidgetInflater>().apply {
-                    whenever(inflateAppWidget(any())).thenReturn(inflationResult)
-                }
-            val packageUserKey = PackageUserKey("com.google.android.testApp", mUserHandle)
-            mInstallingPkgs[packageUserKey] = PackageInstaller.SessionInfo()
-
-            // When
-            itemProcessorUnderTest =
-                createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
-            itemProcessorUnderTest.processItem()
-
-            // Then
-            val expectedWidgetInfo =
-                LauncherAppWidgetInfo().apply {
-                    appWidgetId = expectedAppWidgetId
-                    providerName = ComponentName.unflattenFromString(expectedProvider)
-                    restoreStatus = expectedRestoreStatus
-                }
-            verify(
-                    mockCursor
-                        .updater()
-                        .put(Favorites.APPWIDGET_PROVIDER, expectedProvider)
-                        .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
-                        .put(Favorites.RESTORED, expectedRestoreStatus)
-                )
-                .commit()
-            val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
-            verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
-            val actualWidgetInfo = widgetInfoCaptor.value
-            with(actualWidgetInfo) {
-                assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
-                assertThat(restoreStatus).isEqualTo(expectedWidgetInfo.restoreStatus)
-                assertThat(targetComponent).isEqualTo(expectedWidgetInfo.targetComponent)
-                assertThat(appWidgetId).isEqualTo(expectedWidgetInfo.appWidgetId)
-            }
-        } finally {
-            mockitoSession.finishMocking()
-        }
-    }
-
-    @Test
-    @EnableFlags(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
-    fun `When Archived Pending App Widget then checkAndAddItem`() {
-        val mockitoSession =
-            ExtendedMockito.mockitoSession().mockStatic(Utilities::class.java).startMocking()
-        try {
-            // Given
-            val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
-            val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
-            val expectedPackage = expectedComponentName!!.packageName
-            mockPmHelper =
-                mock<PackageManagerHelper>().apply {
-                    whenever(isAppArchived(expectedPackage)).thenReturn(true)
-                }
-            mockCursor =
-                mock<LoaderCursor>().apply {
-                    itemType = ITEM_TYPE_APPWIDGET
-                    id = 1
-                    user = UserHandle(1)
-                    restoreFlag = FLAG_UI_NOT_READY
-                    container = CONTAINER_DESKTOP
-                    whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
-                    whenever(appWidgetProvider).thenReturn(expectedProvider)
-                    whenever(appWidgetId).thenReturn(0)
-                    whenever(spanX).thenReturn(2)
-                    whenever(spanY).thenReturn(1)
-                    whenever(options).thenReturn(0)
-                    whenever(appWidgetSource).thenReturn(20)
-                    whenever(applyCommonProperties(any())).thenCallRealMethod()
-                }
-            mInstallingPkgs = hashMapOf()
-            val inflationResult =
-                WidgetInflater.InflationResult(
-                    type = WidgetInflater.TYPE_PENDING,
-                    widgetInfo = null
-                )
-            mockWidgetInflater =
-                mock<WidgetInflater>().apply {
-                    whenever(inflateAppWidget(any())).thenReturn(inflationResult)
-                }
-            itemProcessorUnderTest =
-                createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
-
-            // When
-            itemProcessorUnderTest.processItem()
-
-            // Then
-            verify(mockCursor).checkAndAddItem(any(), any())
-        } finally {
-            mockitoSession.finishMocking()
-        }
-    }
-
-    @Test
     fun `When widget inflation result is TYPE_DELETE then mark deleted`() {
         // Given
         val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
similarity index 98%
rename from tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
index b3d02be..ae8e966 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemSpaceFinderTest.kt
@@ -21,6 +21,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -29,6 +30,8 @@
 @RunWith(AndroidJUnit4::class)
 class WorkspaceItemSpaceFinderTest : AbstractWorkspaceModelTest() {
 
+    @get:Rule val modelTestRule = ModelTestRule()
+
     private val mItemSpaceFinder = WorkspaceItemSpaceFinder()
 
     @Before
diff --git a/tests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
similarity index 94%
rename from tests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
index b531adb..d860710 100644
--- a/tests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING
+import com.android.launcher3.model.ModelTestRule
 import com.android.launcher3.util.Executors.MODEL_EXECUTOR
 import com.android.launcher3.util.LauncherModelHelper
 import com.android.launcher3.util.PackageUserKey
@@ -35,6 +36,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
+import org.mockito.kotlin.doNothing
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.verify
@@ -43,7 +45,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class InstallSessionTrackerTest {
-    @get:Rule val setFlagsRule = SetFlagsRule()
+    @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
+
+    @get:Rule(order = 1) val modelTestRule = ModelTestRule()
 
     private val mockInstallSessionHelper: InstallSessionHelper = mock()
     private val mockCallback: InstallSessionTracker.Callback = mock()
@@ -200,13 +204,9 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     fun `register triggers registerPackageInstallerSessionCallback for versions from Q`() {
         // Given
-        whenever(
-                launcherApps.registerPackageInstallerSessionCallback(
-                    MODEL_EXECUTOR,
-                    installSessionTracker
-                )
-            )
-            .then { /* no-op */ }
+        doNothing()
+            .whenever(launcherApps)
+            .registerPackageInstallerSessionCallback(MODEL_EXECUTOR, installSessionTracker)
         // When
         installSessionTracker.register()
         // Then
@@ -218,8 +218,9 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     fun `unregister triggers unregisterPackageInstallerSessionCallback for versions from Q`() {
         // Given
-        whenever(launcherApps.unregisterPackageInstallerSessionCallback(installSessionTracker))
-            .then { /* no-op */ }
+        doNothing()
+            .whenever(launcherApps)
+            .unregisterPackageInstallerSessionCallback(installSessionTracker)
         // When
         installSessionTracker.unregister()
         // Then
diff --git a/tests/src/com/android/launcher3/pm/UserCacheTest.kt b/tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt
similarity index 97%
rename from tests/src/com/android/launcher3/pm/UserCacheTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt
index b21219e..482dced 100644
--- a/tests/src/com/android/launcher3/pm/UserCacheTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/pm/UserCacheTest.kt
@@ -20,6 +20,7 @@
 import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.model.ModelTestRule
 import com.android.launcher3.util.Executors.MODEL_EXECUTOR
 import com.android.launcher3.util.LauncherModelHelper
 import com.android.launcher3.util.TestUtil
@@ -27,11 +28,15 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 class UserCacheTest {
+
+    @get:Rule val modelTestRule = ModelTestRule()
+
     private val launcherModelHelper = LauncherModelHelper()
     private val sandboxContext = launcherModelHelper.sandboxContext
     private lateinit var userCache: UserCache
diff --git a/tests/src/com/android/launcher3/testcomponent/TouchEventGenerator.java b/tests/multivalentTests/src/com/android/launcher3/testcomponent/TouchEventGenerator.java
similarity index 100%
rename from tests/src/com/android/launcher3/testcomponent/TouchEventGenerator.java
rename to tests/multivalentTests/src/com/android/launcher3/testcomponent/TouchEventGenerator.java
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index aa7f388..b933ed2 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
 import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT;
 import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT_SMALL;
+import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
 import static com.android.launcher3.Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS;
 import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
@@ -416,7 +417,7 @@
         assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(false);
     }
 
-    @EnableFlags(FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS)
+    @EnableFlags({FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS})
     @Test
     public void applyIconAndLabel_setsImageSpan_whenInactiveArchivedApp() {
         // Given
@@ -452,7 +453,7 @@
         assertThat(actualSpan.getVerticalAlignment()).isEqualTo(ALIGN_CENTER);
     }
 
-    @EnableFlags(FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS)
+    @EnableFlags({FLAG_ENABLE_SUPPORT_FOR_ARCHIVING, FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS})
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
     @Test
     public void applyIconAndLabel_setsBoldDrawable_whenBoldedTextForArchivedApp() {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index f18c02b..2d53e29 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -45,21 +45,24 @@
 import android.util.ArrayMap;
 
 import androidx.test.core.app.ApplicationProvider;
-import androidx.test.uiautomator.UiDevice;
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.testing.TestInformationProvider;
 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
+import java.util.Arrays;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
@@ -85,6 +88,23 @@
     public static final String TEST_ACTIVITY13 = "com.android.launcher3.tests.Activity14";
     public static final String TEST_ACTIVITY14 = "com.android.launcher3.tests.Activity15";
 
+    public static final List<String> ACTIVITY_LIST = Arrays.asList(
+            TEST_ACTIVITY,
+            TEST_ACTIVITY2,
+            TEST_ACTIVITY3,
+            TEST_ACTIVITY4,
+            TEST_ACTIVITY5,
+            TEST_ACTIVITY6,
+            TEST_ACTIVITY7,
+            TEST_ACTIVITY8,
+            TEST_ACTIVITY9,
+            TEST_ACTIVITY10,
+            TEST_ACTIVITY11,
+            TEST_ACTIVITY12,
+            TEST_ACTIVITY13,
+            TEST_ACTIVITY14
+    );
+
     // Authority for providing a test default-workspace-layout data.
     private static final String TEST_PROVIDER_AUTHORITY =
             LauncherModelHelper.class.getName().toLowerCase();
@@ -128,7 +148,9 @@
         icon.eraseColor(Color.RED);
         sp.setAppIcon(icon);
         sp.setAppLabel(pkg);
-        PackageInstaller pi = sandboxContext.getPackageManager().getPackageInstaller();
+        sp.setInstallerPackageName(ApplicationProvider.getApplicationContext().getPackageName());
+        PackageInstaller pi = ApplicationProvider.getApplicationContext().getPackageManager()
+                .getPackageInstaller();
         int sessionId = pi.createSession(sp);
         mDestroyTask.add(() -> pi.abandonSession(sessionId));
         return sessionId;
@@ -164,11 +186,19 @@
     public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder)
             throws Exception {
         InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(sandboxContext);
-        idp.numRows = idp.numColumns = idp.numDatabaseHotseatIcons = DEFAULT_GRID_SIZE;
-        idp.iconBitmapSize = DEFAULT_BITMAP_SIZE;
+        if (idp.numRows == 0 && idp.numColumns == 0) {
+            idp.numRows = idp.numColumns = idp.numDatabaseHotseatIcons = DEFAULT_GRID_SIZE;
+        }
+        if (idp.iconBitmapSize == 0) {
+            idp.iconBitmapSize = DEFAULT_BITMAP_SIZE;
+        }
 
-        UiDevice.getInstance(getInstrumentation()).executeShellCommand(
-                "settings put secure launcher3.layout.provider " + TEST_PROVIDER_AUTHORITY);
+        Settings.Secure.putString(sandboxContext.getContentResolver(), "launcher3.layout.provider",
+                TEST_PROVIDER_AUTHORITY);
+
+        // TODO: use a wrapper class to differentiate the behavior
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        builder.build(new OutputStreamWriter(bos));
         ContentProvider cp = new TestInformationProvider() {
 
             @Override
@@ -177,8 +207,6 @@
                 try {
                     ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
                     AutoCloseOutputStream outputStream = new AutoCloseOutputStream(pipe[1]);
-                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
-                    builder.build(new OutputStreamWriter(bos));
                     outputStream.write(bos.toByteArray());
                     outputStream.flush();
                     outputStream.close();
@@ -189,9 +217,13 @@
             }
         };
         setupProvider(TEST_PROVIDER_AUTHORITY, cp);
+        RoboApiWrapper.INSTANCE.registerInputStream(sandboxContext.getContentResolver(),
+                ModelDbController.getLayoutUri(TEST_PROVIDER_AUTHORITY, sandboxContext),
+                ()-> new ByteArrayInputStream(bos.toByteArray()));
+
         mDestroyTask.add(() -> runOnExecutorSync(MODEL_EXECUTOR, () ->
-                UiDevice.getInstance(getInstrumentation()).executeShellCommand(
-                        "settings delete secure launcher3.layout.provider")));
+                Settings.Secure.putString(sandboxContext.getContentResolver(),
+                        "launcher3.layout.provider", "")));
         return this;
     }
 
@@ -203,7 +235,7 @@
         MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get();
 
         Executors.MODEL_EXECUTOR.submit(() -> { }).get();
-        MAIN_EXECUTOR.submit(() -> { }).get();
+        getInstrumentation().waitForIdleSync();
         MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
     }
 
diff --git a/tests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt
similarity index 86%
rename from tests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt
index 011d138..d26c4d4 100644
--- a/tests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ViewOnDrawExecutorTest.kt
@@ -32,7 +32,7 @@
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.reset
 import org.mockito.kotlin.same
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
 
 @RunWith(AndroidJUnit4::class)
 class ViewOnDrawExecutorTest<T> where T : View, T : PageIndicator {
@@ -77,8 +77,8 @@
         underTest.attachTo(launcher)
 
         verify(workspace).addOnAttachStateChangeListener(same(underTest))
-        verifyZeroInteractions(viewTreeObserver)
-        verifyZeroInteractions(rootView)
+        verifyNoMoreInteractions(viewTreeObserver)
+        verifyNoMoreInteractions(rootView)
     }
 
     @Test
@@ -100,8 +100,8 @@
 
         underTest.onViewAttachedToWindow(rootView)
 
-        verifyZeroInteractions(viewTreeObserver)
-        verifyZeroInteractions(rootView)
+        verifyNoMoreInteractions(viewTreeObserver)
+        verifyNoMoreInteractions(rootView)
     }
 
     @Test
@@ -117,10 +117,10 @@
     fun run_before_onDraw_noOp() {
         underTest.run()
 
-        verifyZeroInteractions(runnable)
-        verifyZeroInteractions(viewTreeObserver)
-        verifyZeroInteractions(workspace)
-        verifyZeroInteractions(consumer)
+        verifyNoMoreInteractions(runnable)
+        verifyNoMoreInteractions(viewTreeObserver)
+        verifyNoMoreInteractions(workspace)
+        verifyNoMoreInteractions(consumer)
     }
 
     @Test
@@ -148,10 +148,10 @@
 
         underTest.run()
 
-        verifyZeroInteractions(runnable)
-        verifyZeroInteractions(viewTreeObserver)
-        verifyZeroInteractions(workspace)
-        verifyZeroInteractions(consumer)
+        verifyNoMoreInteractions(runnable)
+        verifyNoMoreInteractions(viewTreeObserver)
+        verifyNoMoreInteractions(workspace)
+        verifyNoMoreInteractions(consumer)
     }
 
     @Test
@@ -160,7 +160,7 @@
 
         verify(runnable).run()
         verify(consumer).accept(underTest)
-        verifyZeroInteractions(workspace)
+        verifyNoMoreInteractions(workspace)
     }
 
     @Test
@@ -179,8 +179,8 @@
     fun cancel_notRun() {
         underTest.cancel()
 
-        verifyZeroInteractions(runnable)
+        verifyNoMoreInteractions(runnable)
         verify(consumer).accept(underTest)
-        verifyZeroInteractions(workspace)
+        verifyNoMoreInteractions(workspace)
     }
 }
diff --git a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
index d9af07a..05f626d 100644
--- a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
@@ -58,7 +58,8 @@
 @RunWith(AndroidJUnit4::class)
 class PackageUpdatedTaskTest {
 
-    @get:Rule val setFlagsRule = SetFlagsRule()
+    @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
+    @get:Rule(order = 1) val modelTestRule = ModelTestRule()
 
     private val mUser = UserHandle(0)
     private val mDataModel: BgDataModel = BgDataModel()
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
new file mode 100644
index 0000000..b93c305
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.LauncherApps
+import android.content.pm.PackageInstaller
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.util.LongSparseArray
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+import com.android.launcher3.Utilities
+import com.android.launcher3.model.data.IconRequestInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
+import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.PackageManagerHelper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.UserIconInfo
+import com.android.launcher3.widget.WidgetInflater
+import com.android.launcher3.widget.WidgetSections
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+@RunWith(AndroidJUnit4::class)
+class WorkspaceItemProcessorExtraTest {
+
+    @Mock private lateinit var mockIconRequestInfo: IconRequestInfo<WorkspaceItemInfo>
+    @Mock private lateinit var mockWorkspaceInfo: WorkspaceItemInfo
+    @Mock private lateinit var mockBgDataModel: BgDataModel
+    @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var mockAppState: LauncherAppState
+    @Mock private lateinit var mockPmHelper: PackageManagerHelper
+    @Mock private lateinit var mockLauncherApps: LauncherApps
+    @Mock private lateinit var mockCursor: LoaderCursor
+    @Mock private lateinit var mockUserCache: UserCache
+    @Mock private lateinit var mockUserManagerState: UserManagerState
+    @Mock private lateinit var mockWidgetInflater: WidgetInflater
+
+    private var intent: Intent = Intent()
+    private var mUserHandle: UserHandle = UserHandle(0)
+    private var mIconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf()
+    private var mComponentName: ComponentName = ComponentName("package", "class")
+    private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray()
+    private var mKeyToPinnedShortcutsMap: MutableMap<ShortcutKey, ShortcutInfo> = mutableMapOf()
+    private var mInstallingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf()
+    private var mAllDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+    private var mWidgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> =
+        mutableMapOf()
+    private var mPendingPackages: MutableSet<PackageUserKey> = mutableSetOf()
+
+    private lateinit var itemProcessorUnderTest: WorkspaceItemProcessor
+
+    @Before
+    fun setup() {
+        mUserHandle = UserHandle(0)
+        mockIconRequestInfo = mock<IconRequestInfo<WorkspaceItemInfo>>()
+        mockWorkspaceInfo = mock<WorkspaceItemInfo>()
+        mockBgDataModel = mock<BgDataModel>()
+        mComponentName = ComponentName("package", "class")
+        mUnlockedUsersArray = LongSparseArray<Boolean>(1).apply { put(101, true) }
+        intent =
+            Intent().apply {
+                component = mComponentName
+                `package` = "pkg"
+                putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "")
+            }
+        mockContext =
+            mock<Context>().apply {
+                whenever(packageManager).thenReturn(mock())
+                whenever(packageManager.getUserBadgedLabel(any(), any())).thenReturn("")
+                whenever(applicationContext).thenReturn(ApplicationProvider.getApplicationContext())
+            }
+        mockAppState =
+            mock<LauncherAppState>().apply {
+                whenever(context).thenReturn(mockContext)
+                whenever(iconCache).thenReturn(mock())
+                whenever(iconCache.getShortcutIcon(any(), any(), any())).then {}
+            }
+        mockPmHelper =
+            mock<PackageManagerHelper>().apply {
+                whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
+                    .thenReturn(intent)
+            }
+        mockLauncherApps =
+            mock<LauncherApps>().apply {
+                whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+                whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true)
+            }
+        mockCursor =
+            Mockito.mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply {
+                user = mUserHandle
+                itemType = ITEM_TYPE_APPLICATION
+                id = 1
+                restoreFlag = 1
+                serialNumber = 101
+                whenever(parseIntent()).thenReturn(intent)
+                whenever(markRestored()).doAnswer { restoreFlag = 0 }
+                whenever(updater().put(Favorites.INTENT, intent.toUri(0)).commit()).thenReturn(1)
+                whenever(getAppShortcutInfo(any(), any(), any(), any()))
+                    .thenReturn(mockWorkspaceInfo)
+                whenever(createIconRequestInfo(any(), any())).thenReturn(mockIconRequestInfo)
+            }
+        mockUserCache =
+            mock<UserCache>().apply {
+                val userIconInfo =
+                    mock<UserIconInfo>().apply { whenever(isPrivate).thenReturn(false) }
+                whenever(getUserInfo(any())).thenReturn(userIconInfo)
+            }
+
+        mockUserManagerState = mock<UserManagerState>()
+        mockWidgetInflater = mock<WidgetInflater>()
+        mKeyToPinnedShortcutsMap = mutableMapOf()
+        mInstallingPkgs = hashMapOf()
+        mAllDeepShortcuts = mutableListOf()
+        mWidgetProvidersMap = mutableMapOf()
+        mIconRequestInfos = mutableListOf()
+        mPendingPackages = mutableSetOf()
+    }
+
+    @Test
+    fun `When Pending App Widget has not started restore then update db and add item`() {
+
+        val mockitoSession =
+            ExtendedMockito.mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .mockStatic(WidgetSections::class.java)
+                .startMocking()
+        try {
+            // Given
+            val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+            val expectedComponentName =
+                ComponentName.unflattenFromString(expectedProvider)!!.flattenToString()
+            val expectedRestoreStatus = FLAG_UI_NOT_READY or FLAG_RESTORE_STARTED
+            val expectedAppWidgetId = 0
+            mockCursor.apply {
+                itemType = ITEM_TYPE_APPWIDGET
+                user = mUserHandle
+                restoreFlag = FLAG_UI_NOT_READY
+                container = CONTAINER_DESKTOP
+                whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+                whenever(appWidgetProvider).thenReturn(expectedProvider)
+                whenever(appWidgetId).thenReturn(expectedAppWidgetId)
+                whenever(spanX).thenReturn(2)
+                whenever(spanY).thenReturn(1)
+                whenever(options).thenReturn(0)
+                whenever(appWidgetSource).thenReturn(20)
+                whenever(applyCommonProperties(any())).thenCallRealMethod()
+                whenever(
+                        updater()
+                            .put(Favorites.APPWIDGET_PROVIDER, expectedComponentName)
+                            .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
+                            .put(Favorites.RESTORED, expectedRestoreStatus)
+                            .commit()
+                    )
+                    .thenReturn(1)
+            }
+            val inflationResult =
+                WidgetInflater.InflationResult(
+                    type = WidgetInflater.TYPE_PENDING,
+                    widgetInfo = null
+                )
+            mockWidgetInflater =
+                mock<WidgetInflater>().apply {
+                    whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+                }
+            val packageUserKey = PackageUserKey("com.google.android.testApp", mUserHandle)
+            mInstallingPkgs[packageUserKey] = PackageInstaller.SessionInfo()
+
+            // When
+            itemProcessorUnderTest =
+                createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+            itemProcessorUnderTest.processItem()
+
+            // Then
+            val expectedWidgetInfo =
+                LauncherAppWidgetInfo().apply {
+                    appWidgetId = expectedAppWidgetId
+                    providerName = ComponentName.unflattenFromString(expectedProvider)
+                    restoreStatus = expectedRestoreStatus
+                }
+            verify(
+                    mockCursor
+                        .updater()
+                        .put(Favorites.APPWIDGET_PROVIDER, expectedProvider)
+                        .put(Favorites.APPWIDGET_ID, expectedAppWidgetId)
+                        .put(Favorites.RESTORED, expectedRestoreStatus)
+                )
+                .commit()
+            val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
+            verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+            val actualWidgetInfo = widgetInfoCaptor.value
+            with(actualWidgetInfo) {
+                assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
+                assertThat(restoreStatus).isEqualTo(expectedWidgetInfo.restoreStatus)
+                assertThat(targetComponent).isEqualTo(expectedWidgetInfo.targetComponent)
+                assertThat(appWidgetId).isEqualTo(expectedWidgetInfo.appWidgetId)
+            }
+        } finally {
+            mockitoSession.finishMocking()
+        }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
+    fun `When Archived Pending App Widget then checkAndAddItem`() {
+        val mockitoSession =
+            ExtendedMockito.mockitoSession().mockStatic(Utilities::class.java).startMocking()
+        try {
+            // Given
+            val expectedProvider = "com.google.android.testApp/com.android.testApp.testAppProvider"
+            val expectedComponentName = ComponentName.unflattenFromString(expectedProvider)
+            val expectedPackage = expectedComponentName!!.packageName
+            mockPmHelper =
+                mock<PackageManagerHelper>().apply {
+                    whenever(isAppArchived(expectedPackage)).thenReturn(true)
+                }
+            mockCursor =
+                mock<LoaderCursor>().apply {
+                    itemType = ITEM_TYPE_APPWIDGET
+                    id = 1
+                    user = UserHandle(1)
+                    restoreFlag = FLAG_UI_NOT_READY
+                    container = CONTAINER_DESKTOP
+                    whenever(isOnWorkspaceOrHotseat).thenCallRealMethod()
+                    whenever(appWidgetProvider).thenReturn(expectedProvider)
+                    whenever(appWidgetId).thenReturn(0)
+                    whenever(spanX).thenReturn(2)
+                    whenever(spanY).thenReturn(1)
+                    whenever(options).thenReturn(0)
+                    whenever(appWidgetSource).thenReturn(20)
+                    whenever(applyCommonProperties(any())).thenCallRealMethod()
+                }
+            mInstallingPkgs = hashMapOf()
+            val inflationResult =
+                WidgetInflater.InflationResult(
+                    type = WidgetInflater.TYPE_PENDING,
+                    widgetInfo = null
+                )
+            mockWidgetInflater =
+                mock<WidgetInflater>().apply {
+                    whenever(inflateAppWidget(any())).thenReturn(inflationResult)
+                }
+            itemProcessorUnderTest =
+                createWorkspaceItemProcessorUnderTest(widgetProvidersMap = mWidgetProvidersMap)
+
+            // When
+            itemProcessorUnderTest.processItem()
+
+            // Then
+            verify(mockCursor).checkAndAddItem(any(), any())
+        } finally {
+            mockitoSession.finishMocking()
+        }
+    }
+
+    private fun createWorkspaceItemProcessorUnderTest(
+        cursor: LoaderCursor = mockCursor,
+        memoryLogger: LoaderMemoryLogger? = null,
+        userCache: UserCache = mockUserCache,
+        userManagerState: UserManagerState = mockUserManagerState,
+        launcherApps: LauncherApps = mockLauncherApps,
+        shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo> = mKeyToPinnedShortcutsMap,
+        app: LauncherAppState = mockAppState,
+        bgDataModel: BgDataModel = mockBgDataModel,
+        widgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> = mWidgetProvidersMap,
+        widgetInflater: WidgetInflater = mockWidgetInflater,
+        pmHelper: PackageManagerHelper = mockPmHelper,
+        iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mIconRequestInfos,
+        isSdCardReady: Boolean = false,
+        pendingPackages: MutableSet<PackageUserKey> = mPendingPackages,
+        unlockedUsers: LongSparseArray<Boolean> = mUnlockedUsersArray,
+        installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = mInstallingPkgs,
+        allDeepShortcuts: MutableList<ShortcutInfo> = mAllDeepShortcuts
+    ) =
+        WorkspaceItemProcessor(
+            c = cursor,
+            memoryLogger = memoryLogger,
+            userCache = userCache,
+            userManagerState = userManagerState,
+            launcherApps = launcherApps,
+            app = app,
+            bgDataModel = bgDataModel,
+            widgetProvidersMap = widgetProvidersMap,
+            widgetInflater = widgetInflater,
+            pmHelper = pmHelper,
+            unlockedUsers = unlockedUsers,
+            iconRequestInfos = iconRequestInfos,
+            pendingPackages = pendingPackages,
+            isSdCardReady = isSdCardReady,
+            shortcutKeyToPinnedShortcuts = shortcutKeyToPinnedShortcuts,
+            installingPkgs = installingPkgs,
+            allDeepShortcuts = allDeepShortcuts
+        )
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
similarity index 100%
rename from tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
rename to tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
diff --git a/tests/src/com/android/launcher3/util/RoboApiWrapper.kt b/tests/src/com/android/launcher3/util/RoboApiWrapper.kt
new file mode 100644
index 0000000..583652d
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/RoboApiWrapper.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.ContentResolver
+import android.net.Uri
+import android.os.Looper
+import java.io.InputStream
+import java.util.function.Supplier
+
+object RoboApiWrapper {
+
+    fun initialize() {}
+
+    fun registerInputStream(
+        contentResolver: ContentResolver,
+        uri: Uri,
+        inputStreamSupplier: Supplier<InputStream>
+    ) {}
+
+    fun waitForLooperSync(looper: Looper) {}
+}
diff --git a/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt b/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt
new file mode 100644
index 0000000..9232268
--- /dev/null
+++ b/tests/src_deviceless/com/android/launcher3/util/RoboApiWrapper.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.ComponentName
+import android.content.ContentResolver
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.net.Uri
+import android.os.Looper
+import android.os.Process
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.InputStream
+import java.util.function.Supplier
+import org.mockito.Mockito
+import org.mockito.kotlin.whenever
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.Shadows
+
+object RoboApiWrapper {
+
+    fun initialize() {
+        Shadows.shadowOf(
+                RuntimeEnvironment.getApplication().getSystemService(LauncherApps::class.java)
+            )
+            .addEnabledPackage(
+                Process.myUserHandle(),
+                InstrumentationRegistry.getInstrumentation().context.packageName
+            )
+        LauncherModelHelper.ACTIVITY_LIST.forEach {
+            installApp(ComponentName(InstrumentationRegistry.getInstrumentation().context, it))
+        }
+    }
+
+    private fun installApp(componentName: ComponentName) {
+        val app = RuntimeEnvironment.getApplication()
+        val user = Process.myUserHandle()
+
+        val pm = Shadows.shadowOf(app.packageManager)
+        val ai = pm.addActivityIfNotPresent(componentName)
+        pm.addIntentFilterForActivity(
+            componentName,
+            IntentFilter(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
+        )
+
+        val li = Mockito.mock(LauncherActivityInfo::class.java)
+        val appInfo = ApplicationInfo().apply { flags = 0 }
+        Mockito.doReturn(ai).whenever(li).activityInfo
+        Mockito.doReturn(appInfo).whenever(li).applicationInfo
+        Mockito.doReturn(user).whenever(li).user
+        Mockito.doReturn(1f).whenever(li).loadingProgress
+        Mockito.doReturn(componentName).whenever(li).componentName
+
+        Shadows.shadowOf(app.getSystemService(LauncherApps::class.java)).apply {
+            addActivity(user, li)
+            addEnabledPackage(user, componentName.packageName)
+            setActivityEnabled(user, componentName)
+            addApplicationInfo(user, componentName.packageName, ai.applicationInfo)
+        }
+    }
+
+    fun registerInputStream(
+        contentResolver: ContentResolver,
+        uri: Uri,
+        inputStreamSupplier: Supplier<InputStream>
+    ) {
+        Shadows.shadowOf(contentResolver).registerInputStreamSupplier(uri, inputStreamSupplier)
+    }
+
+    fun waitForLooperSync(looper: Looper) {
+        Shadows.shadowOf(looper).runToEndOfTasks()
+    }
+}