From 9ae9b600439c8ffa4a1852d4535164f234041e40 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 18 Dec 2019 19:29:00 +0530 Subject: [PATCH] Updating Robolectric tests > Adding multi-thread support > Simulating actual loader loading flow > Moving some android tests to robolectic Change-Id: Ie17a448f20e8a4b1f18ecc33d22054bbf9e18729 --- .../launcher3/icons/cache/BaseIconCache.java | 5 +- robolectric_tests/Android.mk | 12 +- .../config/robolectric.properties | 3 +- .../launcher3/config/FlagOverrideRule.java | 23 +- .../config/FlagOverrideSampleTest.java | 4 +- .../launcher3/logging/FileLogTest.java | 5 +- .../model/AddWorkspaceItemsTaskTest.java | 107 ++++--- .../model/BaseGridChangesTestCase.java | 121 -------- .../model/BaseModelUpdateTaskTestCase.java | 231 -------------- .../model/CacheDataUpdatedTaskTest.java | 82 ++++- .../model/DbDowngradeHelperTest.java | 4 +- .../model/DefaultLayoutProviderTest.java | 80 ++--- .../launcher3/model/GridBackupTableTest.java | 32 +- .../model/GridSizeMigrationTaskTest.java | 55 ++-- .../launcher3/model/LoaderCursorTest.java | 82 ++--- .../PackageInstallStateChangedTaskTest.java | 25 +- .../launcher3/popup/PopupPopulatorTest.java | 5 +- .../launcher3/provider/RestoreDbTaskTest.java | 26 +- .../shadows/LShadowAppWidgetManager.java | 48 +++ .../launcher3/shadows/LShadowBitmap.java | 36 +++ .../shadows/LShadowLauncherApps.java | 2 +- .../shadows/ShadowLooperExecutor.java | 38 +-- .../ShadowMainThreadInitializedObject.java | 61 ++++ .../shadows/ShadowTogglableFlag.java | 38 +++ .../launcher3/util/GridOccupancyTest.java | 3 +- .../android/launcher3/util/IntArrayTest.java | 3 +- .../android/launcher3/util/IntSetTest.java | 4 +- .../launcher3/util/LauncherModelHelper.java | 288 ++++++++++++++++++ .../util/LauncherRoboTestRunner.java | 90 ++++++ .../widget/WidgetsListAdapterTest.java | 38 +-- src/com/android/launcher3/LauncherModel.java | 14 +- src/com/android/launcher3/util/Executors.java | 5 +- .../launcher3/util/LooperExecutor.java | 10 +- 33 files changed, 960 insertions(+), 620 deletions(-) delete mode 100644 robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java delete mode 100644 robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java rename {tests => robolectric_tests}/src/com/android/launcher3/model/LoaderCursorTest.java (76%) rename {tests => robolectric_tests}/src/com/android/launcher3/provider/RestoreDbTaskTest.java (78%) create mode 100644 robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java create mode 100644 robolectric_tests/src/com/android/launcher3/shadows/LShadowBitmap.java create mode 100644 robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java create mode 100644 robolectric_tests/src/com/android/launcher3/shadows/ShadowTogglableFlag.java create mode 100644 robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java create mode 100644 robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java rename {tests => robolectric_tests}/src/com/android/launcher3/widget/WidgetsListAdapterTest.java (84%) diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java index 6f63d88722..e807791d94 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java @@ -45,6 +45,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.BitmapInfo; @@ -250,9 +251,9 @@ public abstract class BaseIconCache { * @param replaceExisting if true, it will recreate the bitmap even if it already exists in * the memory. This is useful then the previous bitmap was created using * old data. - * package private */ - protected synchronized void addIconToDBAndMemCache(T object, CachingLogic cachingLogic, + @VisibleForTesting + public synchronized void addIconToDBAndMemCache(T object, CachingLogic cachingLogic, PackageInfo info, long userSerial, boolean replaceExisting) { UserHandle user = cachingLogic.getUser(object); ComponentName componentName = cachingLogic.getComponent(object); diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk index 310d43cba4..86a6e8ce63 100644 --- a/robolectric_tests/Android.mk +++ b/robolectric_tests/Android.mk @@ -19,6 +19,8 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := LauncherRoboTests +LOCAL_MODULE_CLASS := JAVA_LIBRARIES + LOCAL_SDK_VERSION := current LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_STATIC_JAVA_LIBRARIES := \ @@ -34,6 +36,9 @@ LOCAL_JAVA_RESOURCE_DIRS := resources config LOCAL_INSTRUMENTATION_FOR := Launcher3 LOCAL_MODULE_TAGS := optional +# Generate test_config.properties +include external/robolectric-shadows/gen_test_config.mk + include $(BUILD_STATIC_JAVA_LIBRARY) ############################################ @@ -43,14 +48,11 @@ include $(CLEAR_VARS) LOCAL_MODULE := RunLauncherRoboTests LOCAL_SDK_VERSION := current -LOCAL_JAVA_LIBRARIES := \ - LauncherRoboTests +LOCAL_JAVA_LIBRARIES := LauncherRoboTests LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res - LOCAL_TEST_PACKAGE := Launcher3 - -LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src \ +LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src LOCAL_ROBOTEST_TIMEOUT := 36000 diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties index e0d6e53acd..932b01b9eb 100644 --- a/robolectric_tests/config/robolectric.properties +++ b/robolectric_tests/config/robolectric.properties @@ -1,2 +1 @@ -manifest=packages/apps/Launcher3/AndroidManifest.xml -sdk=26 +sdk=28 diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java index 4bb9a53fd4..d33fecd0cb 100644 --- a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java +++ b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java @@ -14,6 +14,7 @@ import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @@ -35,6 +36,8 @@ import java.util.stream.Collectors; */ public final class FlagOverrideRule implements TestRule { + private final HashMap mDefaultOverrides = new HashMap<>(); + /** * Container annotation for handling multiple {@link FlagOverride} annotations. *

@@ -60,6 +63,14 @@ public final class FlagOverrideRule implements TestRule { return new MyStatement(base, description); } + /** + * Sets a default override to apply on all tests + */ + public FlagOverrideRule setOverride(BaseTogglableFlag flag, boolean value) { + mDefaultOverrides.put(flag.getKey(), value); + return this; + } + private class MyStatement extends Statement { private final Statement mBase; @@ -87,11 +98,15 @@ public final class FlagOverrideRule implements TestRule { overrides = ((FlagOverrides) annotation).value(); } } - for (FlagOverride override : overrides) { - BaseTogglableFlag flag = allFlags.get(override.key()); + + HashMap allOverrides = new HashMap<>(mDefaultOverrides); + Arrays.stream(overrides).forEach(o -> allOverrides.put(o.key(), o.value())); + + allOverrides.forEach((key, val) -> { + BaseTogglableFlag flag = allFlags.get(key); changedValues.put(flag, flag.get()); - flag.setForTests(override.value()); - } + flag.setForTests(val); + }); mBase.evaluate(); } finally { // Clear the values diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java index 31a037b165..2a359dfb32 100644 --- a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java +++ b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideSampleTest.java @@ -4,16 +4,16 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.android.launcher3.config.FlagOverrideRule.FlagOverride; +import com.android.launcher3.util.LauncherRoboTestRunner; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; /** * Sample Robolectric test that demonstrates flag-overriding. */ -@RunWith(RobolectricTestRunner.class) +@RunWith(LauncherRoboTestRunner.class) public class FlagOverrideSampleTest { // Check out https://junit.org/junit4/javadoc/4.12/org/junit/Rule.html for more information diff --git a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java index 410a077fe0..48b5a459b7 100644 --- a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java +++ b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java @@ -3,11 +3,12 @@ package com.android.launcher3.logging; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import com.android.launcher3.util.LauncherRoboTestRunner; + import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.util.Scheduler; @@ -20,7 +21,7 @@ import java.util.Calendar; /** * Tests for {@link FileLog} */ -@RunWith(RobolectricTestRunner.class) +@RunWith(LauncherRoboTestRunner.class) public class FileLogTest { private File mTempDir; diff --git a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java index d7a2278463..ea7c137d4e 100644 --- a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java @@ -4,54 +4,70 @@ 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 com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.util.ContentWriter; 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 com.android.launcher3.util.LauncherRoboTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; /** * Tests for {@link AddWorkspaceItemsTask} */ -@RunWith(RobolectricTestRunner.class) -public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { +@RunWith(LauncherRoboTestRunner.class) +@LooperMode(Mode.PAUSED) +public class AddWorkspaceItemsTaskTest { private final ComponentName mComponent1 = new ComponentName("a", "b"); private final ComponentName mComponent2 = new ComponentName("b", "b"); - private IntArray existingScreens; - private IntArray newScreens; - private IntSparseArrayMap screenOccupancy; + private Context mTargetContext; + private InvariantDeviceProfile mIdp; + private LauncherAppState mAppState; + private LauncherModelHelper mModelHelper; + + private IntArray mExistingScreens; + private IntArray mNewScreens; + private IntSparseArrayMap mScreenOccupancy; @Before - public void initData() throws Exception { - existingScreens = new IntArray(); - screenOccupancy = new IntSparseArrayMap<>(); - newScreens = new IntArray(); + public void setup() { + mModelHelper = new LauncherModelHelper(); + mTargetContext = RuntimeEnvironment.application; + mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); + mIdp.numColumns = mIdp.numRows = 5; + mAppState = LauncherAppState.getInstance(mTargetContext); - idp.numColumns = 5; - idp.numRows = 5; + mExistingScreens = new IntArray(); + mScreenOccupancy = new IntSparseArrayMap<>(); + mNewScreens = new IntArray(); } private AddWorkspaceItemsTask newTask(ItemInfo... items) { @@ -70,17 +86,17 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { // 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(appState, bgDataModel, existingScreens, newScreens, 1, 1); + int[] spaceFound = newTask().findSpaceForItem( + mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1); assertEquals(2, spaceFound[0]); - assertTrue(screenOccupancy.get(spaceFound[0]) + assertTrue(mScreenOccupancy.get(spaceFound[0]) .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1)); // Find a larger space - spaceFound = newTask() - .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3); + spaceFound = newTask().findSpaceForItem( + mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3); assertEquals(2, spaceFound[0]); - assertTrue(screenOccupancy.get(spaceFound[0]) + assertTrue(mScreenOccupancy.get(spaceFound[0]) .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3)); } @@ -89,11 +105,11 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { // 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 = existingScreens.clone(); - int[] spaceFound = newTask() - .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3); + IntArray oldScreens = mExistingScreens.clone(); + int[] spaceFound = newTask().findSpaceForItem( + mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3); assertFalse(oldScreens.contains(spaceFound[0])); - assertTrue(newScreens.contains(spaceFound[0])); + assertTrue(mNewScreens.contains(spaceFound[0])); } @Test @@ -105,11 +121,14 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); // Nothing was added - assertTrue(executeTaskForTest(newTask(info)).isEmpty()); + assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty()); } @Test public void testAddItem_some_items_added() throws Exception { + Callbacks callbacks = mock(Callbacks.class); + mModelHelper.getModel().initialize(callbacks); + WorkspaceItemInfo info = new WorkspaceItemInfo(); info.intent = new Intent().setComponent(mComponent1); @@ -119,7 +138,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { // Setup a screen with a hole setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); - executeTaskForTest(newTask(info, info2)).get(0).run(); + mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run(); ArgumentCaptor notAnimated = ArgumentCaptor.forClass(ArrayList.class); ArgumentCaptor animated = ArgumentCaptor.forClass(ArrayList.class); @@ -134,18 +153,23 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { } private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception { - GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows); - occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true); + 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); } - existingScreens.add(screenId); - screenOccupancy.append(screenId, occupancy); + mExistingScreens.add(screenId); + mScreenOccupancy.append(screenId, occupancy); - ExecutorService executor = Executors.newSingleThreadExecutor(); - for (int x = 0; x < idp.numColumns; x++) { - for (int y = 0; y < idp.numRows; y++) { + for (int x = 0; x < mIdp.numColumns; x++) { + for (int y = 0; y < mIdp.numRows; y++) { if (!occupancy.cells[x][y]) { continue; } @@ -157,20 +181,15 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { info.cellX = x; info.cellY = y; info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; - bgDataModel.addItem(targetContext, info, false); + bgDataModel.addItem(mTargetContext, info, false); - executor.execute(() -> { - ContentWriter writer = new ContentWriter(targetContext); - info.writeToValues(writer); - writer.put(Favorites._ID, info.id); - targetContext.getContentResolver().insert(Favorites.CONTENT_URI, - writer.getValues(targetContext)); - }); + ContentWriter writer = new ContentWriter(mTargetContext); + info.writeToValues(writer); + writer.put(Favorites._ID, info.id); + mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI, + writer.getValues(mTargetContext)); } } - - executor.submit(() -> null).get(); - executor.shutdown(); return startId; } } diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java deleted file mode 100644 index 07834fcd0b..0000000000 --- a/robolectric_tests/src/com/android/launcher3/model/BaseGridChangesTestCase.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.android.launcher3.model; - -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.database.sqlite.SQLiteDatabase; - -import com.android.launcher3.LauncherProvider; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.util.TestLauncherProvider; - -import org.junit.Before; -import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.shadows.ShadowContentResolver; -import org.robolectric.shadows.ShadowLog; - -public abstract class BaseGridChangesTestCase { - - - public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP; - public static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT; - - public static final int APP_ICON = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; - public static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; - public static final int NO__ICON = -1; - - public static final String TEST_PACKAGE = "com.android.launcher3.validpackage"; - - public Context mContext; - public TestLauncherProvider mProvider; - public SQLiteDatabase mDb; - - @Before - public void setUpBaseCase() { - ShadowLog.stream = System.out; - - mContext = RuntimeEnvironment.application; - mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class); - ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider); - mDb = mProvider.getDb(); - } - - /** - * Adds a dummy item in the DB. - * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for - * folder (where the type represents the number of items in the folder). - */ - public int addItem(int type, int screen, int container, int x, int y) { - int id = LauncherSettings.Settings.call(mContext.getContentResolver(), - LauncherSettings.Settings.METHOD_NEW_ITEM_ID) - .getInt(LauncherSettings.Settings.EXTRA_VALUE); - - ContentValues values = new ContentValues(); - values.put(LauncherSettings.Favorites._ID, id); - values.put(LauncherSettings.Favorites.CONTAINER, container); - values.put(LauncherSettings.Favorites.SCREEN, screen); - values.put(LauncherSettings.Favorites.CELLX, x); - values.put(LauncherSettings.Favorites.CELLY, y); - values.put(LauncherSettings.Favorites.SPANX, 1); - values.put(LauncherSettings.Favorites.SPANY, 1); - - if (type == APP_ICON || type == SHORTCUT) { - values.put(LauncherSettings.Favorites.ITEM_TYPE, type); - values.put(LauncherSettings.Favorites.INTENT, - new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0)); - } else { - values.put(LauncherSettings.Favorites.ITEM_TYPE, - LauncherSettings.Favorites.ITEM_TYPE_FOLDER); - // Add folder items. - for (int i = 0; i < type; i++) { - addItem(APP_ICON, 0, id, 0, 0); - } - } - - mContext.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values); - return id; - } - - public int[][][] createGrid(int[][][] typeArray) { - return createGrid(typeArray, 1); - } - - /** - * Initializes the DB with dummy elements to represent the provided grid structure. - * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for - * type definitions. The first dimension represents the screens and the next - * two represent the workspace grid. - * @param startScreen First screen id from where the icons will be added. - * @return the same grid representation where each entry is the corresponding item id. - */ - public int[][][] createGrid(int[][][] typeArray, int startScreen) { - LauncherSettings.Settings.call(mContext.getContentResolver(), - LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); - int[][][] ids = new int[typeArray.length][][]; - - for (int i = 0; i < typeArray.length; i++) { - // Add screen to DB - int screenId = startScreen + i; - - // Keep the screen id counter up to date - LauncherSettings.Settings.call(mContext.getContentResolver(), - LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); - - ids[i] = new int[typeArray[i].length][]; - for (int y = 0; y < typeArray[i].length; y++) { - ids[i][y] = new int[typeArray[i][y].length]; - for (int x = 0; x < typeArray[i][y].length; x++) { - if (typeArray[i][y][x] < 0) { - // Empty cell - ids[i][y][x] = -1; - } else { - ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y); - } - } - } - } - - return ids; - } -} diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java deleted file mode 100644 index 012258d7a5..0000000000 --- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ /dev/null @@ -1,231 +0,0 @@ -package com.android.launcher3.model; - -import static com.android.launcher3.shadows.ShadowLooperExecutor.reinitializeStaticExecutors; - -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Color; -import android.os.Process; -import android.os.UserHandle; - -import androidx.annotation.NonNull; - -import com.android.launcher3.AppFilter; -import com.android.launcher3.AppInfo; -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.ItemInfo; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; -import com.android.launcher3.LauncherModel.ModelUpdateTask; -import com.android.launcher3.LauncherProvider; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.IconCache; -import com.android.launcher3.icons.cache.CachingLogic; -import com.android.launcher3.model.BgDataModel.Callbacks; -import com.android.launcher3.pm.InstallSessionHelper; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.TestLauncherProvider; - -import org.junit.Before; -import org.mockito.ArgumentCaptor; -import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.shadows.ShadowContentResolver; -import org.robolectric.shadows.ShadowLog; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.function.Supplier; - -/** - * Base class for writing tests for Model update tasks. - */ -public class BaseModelUpdateTaskTestCase { - - public final HashMap> fieldCache = new HashMap<>(); - public TestLauncherProvider provider; - - public Context targetContext; - public UserHandle myUser; - - public InvariantDeviceProfile idp; - public LauncherAppState appState; - public LauncherModel model; - public ModelWriter modelWriter; - public MyIconCache iconCache; - - public BgDataModel bgDataModel; - public AllAppsList allAppsList; - public Callbacks callbacks; - - @Before - public void setUp() throws Exception { - ShadowLog.stream = System.out; - reinitializeStaticExecutors(); - InstallSessionHelper.INSTANCE.initializeForTesting(null); - - provider = Robolectric.setupContentProvider(TestLauncherProvider.class); - ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, provider); - - callbacks = mock(Callbacks.class); - appState = mock(LauncherAppState.class); - model = mock(LauncherModel.class); - modelWriter = mock(ModelWriter.class); - - LauncherAppState.INSTANCE.initializeForTesting(appState); - when(appState.getModel()).thenReturn(model); - when(model.getWriter(anyBoolean(), anyBoolean())).thenReturn(modelWriter); - when(model.getCallback()).thenReturn(callbacks); - - myUser = Process.myUserHandle(); - - bgDataModel = new BgDataModel(); - targetContext = RuntimeEnvironment.application; - - idp = new InvariantDeviceProfile(); - iconCache = new MyIconCache(targetContext, idp); - - allAppsList = new AllAppsList(iconCache, new AppFilter()); - - when(appState.getIconCache()).thenReturn(iconCache); - when(appState.getInvariantDeviceProfile()).thenReturn(idp); - when(appState.getContext()).thenReturn(targetContext); - } - - /** - * Synchronously executes the task and returns all the UI callbacks posted. - */ - public List executeTaskForTest(ModelUpdateTask task) throws Exception { - when(model.isModelLoaded()).thenReturn(true); - - Executor mockExecutor = mock(Executor.class); - - task.init(appState, model, bgDataModel, allAppsList, mockExecutor); - task.run(); - ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); - verify(mockExecutor, atLeast(0)).execute(captor.capture()); - - return captor.getAllValues(); - } - - /** - * Initializes mock data for the test. - */ - public void initializeData(String resourceName) throws Exception { - try (BufferedReader reader = new BufferedReader(new InputStreamReader( - this.getClass().getResourceAsStream(resourceName)))) { - String line; - HashMap classMap = new HashMap<>(); - while((line = reader.readLine()) != null) { - line = line.trim(); - if (line.startsWith("#") || line.isEmpty()) { - continue; - } - String[] commands = line.split(" "); - switch (commands[0]) { - case "classMap": - classMap.put(commands[1], Class.forName(commands[2])); - break; - case "bgItem": - bgDataModel.addItem(targetContext, - (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false); - break; - case "allApps": - allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null); - break; - } - } - } - } - - private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception { - HashMap cache = fieldCache.get(clazz); - if (cache == null) { - cache = new HashMap<>(); - Class c = clazz; - while (c != null) { - for (Field f : c.getDeclaredFields()) { - f.setAccessible(true); - cache.put(f.getName(), f); - } - c = c.getSuperclass(); - } - fieldCache.put(clazz, cache); - } - - Object item = clazz.newInstance(); - for (int i = startIndex; i < fieldDef.length; i++) { - String[] fieldData = fieldDef[i].split("=", 2); - Field f = cache.get(fieldData[0]); - Class type = f.getType(); - if (type == int.class || type == long.class) { - f.set(item, Integer.parseInt(fieldData[1])); - } else if (type == CharSequence.class || type == String.class) { - f.set(item, fieldData[1]); - } else if (type == Intent.class) { - if (!fieldData[1].startsWith("#Intent")) { - fieldData[1] = "#Intent;" + fieldData[1] + ";end"; - } - f.set(item, Intent.parseUri(fieldData[1], 0)); - } else if (type == ComponentName.class) { - f.set(item, ComponentName.unflattenFromString(fieldData[1])); - } else { - throw new Exception("Added parsing logic for " - + f.getName() + " of type " + f.getType()); - } - } - return item; - } - - public static class MyIconCache extends IconCache { - - private final HashMap mCache = new HashMap<>(); - - public MyIconCache(Context context, InvariantDeviceProfile idp) { - super(context, idp); - } - - @Override - protected CacheEntry cacheLocked( - @NonNull ComponentName componentName, - UserHandle user, @NonNull Supplier infoProvider, - @NonNull CachingLogic cachingLogic, - boolean usePackageIcon, boolean useLowResIcon) { - CacheEntry entry = mCache.get(new ComponentKey(componentName, user)); - if (entry == null) { - entry = new CacheEntry(); - entry.bitmap = getDefaultIcon(user); - } - return entry; - } - - public void addCache(ComponentName key, String title) { - CacheEntry entry = new CacheEntry(); - entry.bitmap = BitmapInfo.of(newIcon(), Color.RED); - entry.title = title; - mCache.put(new ComponentKey(key, Process.myUserHandle()), entry); - } - - public Bitmap newIcon() { - return Bitmap.createBitmap(1, 1, Config.ARGB_8888); - } - - @Override - public synchronized BitmapInfo getDefaultIcon(UserHandle user) { - return BitmapInfo.fromBitmap(newIcon()); - } - } -} diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java index 69c5b00c27..f128e24912 100644 --- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java @@ -5,15 +5,34 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Color; +import android.os.Process; +import android.os.UserHandle; +import android.os.UserManager; + +import androidx.annotation.NonNull; + import com.android.launcher3.AppInfo; import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.IconCache; +import com.android.launcher3.icons.cache.CachingLogic; +import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.LauncherRoboTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; import java.util.Arrays; import java.util.HashSet; @@ -21,40 +40,73 @@ import java.util.HashSet; /** * Tests for {@link CacheDataUpdatedTask} */ -@RunWith(RobolectricTestRunner.class) -public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase { +@RunWith(LauncherRoboTestRunner.class) +@LooperMode(Mode.PAUSED) +public class CacheDataUpdatedTaskTest { private static final String NEW_LABEL_PREFIX = "new-label-"; + private LauncherModelHelper mModelHelper; + @Before - public void initData() throws Exception { - initializeData("/cache_data_updated_task_data.txt"); + public void setup() throws Exception { + mModelHelper = new LauncherModelHelper(); + mModelHelper.initializeData("/cache_data_updated_task_data.txt"); + // Add dummy entries in the cache to simulate update - for (ItemInfo info : bgDataModel.itemsIdMap) { - iconCache.addCache(info.getTargetComponent(), NEW_LABEL_PREFIX + info.id); + Context context = RuntimeEnvironment.application; + IconCache iconCache = LauncherAppState.getInstance(context).getIconCache(); + CachingLogic dummyLogic = new CachingLogic() { + @Override + public ComponentName getComponent(ItemInfo info) { + return info.getTargetComponent(); + } + + @Override + public UserHandle getUser(ItemInfo info) { + return info.user; + } + + @Override + public CharSequence getLabel(ItemInfo info) { + return NEW_LABEL_PREFIX + info.id; + } + + @NonNull + @Override + public BitmapInfo loadIcon(Context context, ItemInfo info) { + return BitmapInfo.of(Bitmap.createBitmap(1, 1, Config.ARGB_8888), Color.RED); + } + }; + + UserManager um = context.getSystemService(UserManager.class); + for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) { + iconCache.addIconToDBAndMemCache(info, dummyLogic, new PackageInfo(), + um.getSerialNumberForUser(info.user), true); } } private CacheDataUpdatedTask newTask(int op, String... pkg) { - return new CacheDataUpdatedTask(op, myUser, new HashSet<>(Arrays.asList(pkg))); + return new CacheDataUpdatedTask(op, Process.myUserHandle(), + new HashSet<>(Arrays.asList(pkg))); } @Test public void testCacheUpdate_update_apps() throws Exception { // Clear all icons from apps list so that its easy to check what was updated - for (AppInfo info : allAppsList.data) { + for (AppInfo info : mModelHelper.getAllAppsList().data) { info.bitmap = BitmapInfo.LOW_RES_INFO; } - executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1")); + mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1")); // Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7) // is not updated verifyUpdate(1, 2); // Verify that only app1 var updated in allAppsList - assertFalse(allAppsList.data.isEmpty()); - for (AppInfo info : allAppsList.data) { + assertFalse(mModelHelper.getAllAppsList().data.isEmpty()); + for (AppInfo info : mModelHelper.getAllAppsList().data) { if (info.componentName.getPackageName().equals("app1")) { assertFalse(info.bitmap.isNullOrLowRes()); } else { @@ -65,7 +117,7 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase { @Test public void testSessionUpdate_ignores_normal_apps() throws Exception { - executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1")); + mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1")); // app1 has no restored shortcuts. Verify that nothing was updated. verifyUpdate(); @@ -73,7 +125,7 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase { @Test public void testSessionUpdate_updates_pending_apps() throws Exception { - executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3")); + mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3")); // app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were // were updated @@ -82,7 +134,7 @@ public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase { private void verifyUpdate(Integer... idsUpdated) { HashSet updates = new HashSet<>(Arrays.asList(idsUpdated)); - for (ItemInfo info : bgDataModel.itemsIdMap) { + for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) { if (updates.contains(info.id)) { assertEquals(NEW_LABEL_PREFIX + info.id, info.title); assertFalse(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes()); diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java index b7340cf7d0..1442c55c2e 100644 --- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java @@ -36,11 +36,11 @@ import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherProvider.DatabaseHelper; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; +import com.android.launcher3.util.LauncherRoboTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.io.File; @@ -48,7 +48,7 @@ import java.io.File; /** * Tests for {@link DbDowngradeHelper} */ -@RunWith(RobolectricTestRunner.class) +@RunWith(LauncherRoboTestRunner.class) public class DbDowngradeHelperTest { private static final String SCHEMA_FILE = "test_schema.json"; diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java index 68713d88a8..e0ddcb1298 100644 --- a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java @@ -22,6 +22,7 @@ import static org.robolectric.Shadows.shadowOf; import static org.robolectric.util.ReflectionHelpers.setField; import android.content.ComponentName; +import android.content.Context; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; @@ -31,23 +32,20 @@ import android.provider.Settings; import com.android.launcher3.FolderInfo; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.pm.InstallSessionHelper; -import com.android.launcher3.shadows.LShadowLauncherApps; -import com.android.launcher3.shadows.LShadowUserManager; -import com.android.launcher3.shadows.ShadowLooperExecutor; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.util.Executors; import com.android.launcher3.util.LauncherLayoutBuilder; -import com.android.launcher3.widget.custom.CustomWidgetManager; +import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.LauncherRoboTestRunner; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.LooperMode; import org.robolectric.annotation.LooperMode.Mode; import org.robolectric.shadows.ShadowPackageManager; @@ -61,10 +59,9 @@ import java.util.ArrayList; /** * Tests for layout parser for remote layout */ -@RunWith(RobolectricTestRunner.class) -@Config(shadows = {LShadowUserManager.class, LShadowLauncherApps.class, ShadowLooperExecutor.class}) +@RunWith(LauncherRoboTestRunner.class) @LooperMode(Mode.PAUSED) -public class DefaultLayoutProviderTest extends BaseModelUpdateTaskTestCase { +public class DefaultLayoutProviderTest { private static final String SETTINGS_APP = "com.android.settings"; private static final String TEST_PROVIDER_AUTHORITY = @@ -73,40 +70,37 @@ public class DefaultLayoutProviderTest extends BaseModelUpdateTaskTestCase { private static final int BITMAP_SIZE = 10; private static final int GRID_SIZE = 4; + private LauncherModelHelper mModelHelper; + private Context mTargetContext; + private InvariantDeviceProfile mIdp; + @Before - public void setUp() throws Exception { - super.setUp(); - InvariantDeviceProfile.INSTANCE.initializeForTesting(idp); - CustomWidgetManager.INSTANCE.initializeForTesting(mock(CustomWidgetManager.class)); + public void setUp() { + mModelHelper = new LauncherModelHelper(); + mTargetContext = RuntimeEnvironment.application; - idp.numRows = idp.numColumns = idp.numHotseatIcons = GRID_SIZE; - idp.iconBitmapSize = BITMAP_SIZE; + mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); + mIdp.numRows = mIdp.numColumns = mIdp.numHotseatIcons = GRID_SIZE; + mIdp.iconBitmapSize = BITMAP_SIZE; - provider.setAllowLoadDefaultFavorites(true); - Settings.Secure.putString(targetContext.getContentResolver(), + mModelHelper.provider.setAllowLoadDefaultFavorites(true); + Settings.Secure.putString(mTargetContext.getContentResolver(), "launcher3.layout.provider", TEST_PROVIDER_AUTHORITY); - ShadowPackageManager spm = shadowOf(targetContext.getPackageManager()); + ShadowPackageManager spm = shadowOf(mTargetContext.getPackageManager()); spm.addProviderIfNotPresent(new ComponentName("com.test", "Dummy")).authority = TEST_PROVIDER_AUTHORITY; spm.addActivityIfNotPresent(new ComponentName(SETTINGS_APP, SETTINGS_APP)); } - @After - public void cleanup() { - InvariantDeviceProfile.INSTANCE.initializeForTesting(null); - CustomWidgetManager.INSTANCE.initializeForTesting(null); - InstallSessionHelper.INSTANCE.initializeForTesting(null); - } - @Test public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception { writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0) .putApp(SETTINGS_APP, SETTINGS_APP)); // Verify one item in hotseat - assertEquals(1, bgDataModel.workspaceItems.size()); - ItemInfo info = bgDataModel.workspaceItems.get(0); + assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); + ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container); assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType); } @@ -120,8 +114,8 @@ public class DefaultLayoutProviderTest extends BaseModelUpdateTaskTestCase { .build()); // Verify folder - assertEquals(1, bgDataModel.workspaceItems.size()); - ItemInfo info = bgDataModel.workspaceItems.get(0); + assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); + ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType); assertEquals(3, ((FolderInfo) info).contents.size()); } @@ -134,7 +128,7 @@ public class DefaultLayoutProviderTest extends BaseModelUpdateTaskTestCase { SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); params.setAppPackageName(pendingAppPkg); - PackageInstaller installer = targetContext.getPackageManager().getPackageInstaller(); + PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller(); int sessionId = installer.createSession(params); SessionInfo sessionInfo = installer.getSessionInfo(sessionId); setField(sessionInfo, "installerPackageName", "com.test"); @@ -144,8 +138,8 @@ public class DefaultLayoutProviderTest extends BaseModelUpdateTaskTestCase { .putWidget(pendingAppPkg, "DummyWidget", 2, 2)); // Verify widget - assertEquals(1, bgDataModel.appWidgets.size()); - ItemInfo info = bgDataModel.appWidgets.get(0); + assertEquals(1, mModelHelper.getBgDataModel().appWidgets.size()); + ItemInfo info = mModelHelper.getBgDataModel().appWidgets.get(0); assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType); assertEquals(2, info.spanX); assertEquals(2, info.spanY); @@ -155,13 +149,21 @@ public class DefaultLayoutProviderTest extends BaseModelUpdateTaskTestCase { ByteArrayOutputStream bos = new ByteArrayOutputStream(); builder.build(new OutputStreamWriter(bos)); - Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, targetContext); - shadowOf(targetContext.getContentResolver()).registerInputStream(layoutUri, + Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, mTargetContext); + shadowOf(mTargetContext.getContentResolver()).registerInputStream(layoutUri, new ByteArrayInputStream(bos.toByteArray())); - LoaderResults results = new LoaderResults(appState, bgDataModel, allAppsList, 0, - new WeakReference<>(callbacks)); - LoaderTask task = new LoaderTask(appState, allAppsList, bgDataModel, results); + LoaderResults results = new LoaderResults( + LauncherAppState.getInstance(mTargetContext), + mModelHelper.getBgDataModel(), + mModelHelper.getAllAppsList(), + 0, + new WeakReference<>(mock(Callbacks.class))); + LoaderTask task = new LoaderTask( + LauncherAppState.getInstance(mTargetContext), + mModelHelper.getAllAppsList(), + mModelHelper.getBgDataModel(), + results); Executors.MODEL_EXECUTOR.submit(() -> task.loadWorkspace(new ArrayList<>())).get(); } } diff --git a/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java index 53287a98b8..f46b849a84 100644 --- a/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java @@ -6,33 +6,53 @@ import static android.database.DatabaseUtils.queryNumEntries; import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME; import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; import static com.android.launcher3.provider.LauncherDbUtils.tableExists; +import static com.android.launcher3.util.LauncherModelHelper.APP_ICON; +import static com.android.launcher3.util.LauncherModelHelper.DESKTOP; +import static com.android.launcher3.util.LauncherModelHelper.NO__ICON; +import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; import android.graphics.Point; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Settings; +import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.LauncherRoboTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; /** * Unit tests for {@link GridBackupTable} */ -@RunWith(RobolectricTestRunner.class) -public class GridBackupTableTest extends BaseGridChangesTestCase { +@RunWith(LauncherRoboTestRunner.class) +public class GridBackupTableTest { private static final int BACKUP_ITEM_COUNT = 12; + private LauncherModelHelper mModelHelper; + private Context mContext; + private SQLiteDatabase mDb; + @Before - public void setupGridData() { - createGrid(new int[][][]{{ + public void setUp() { + mModelHelper = new LauncherModelHelper(); + mContext = RuntimeEnvironment.application; + mDb = mModelHelper.provider.getDb(); + + setupGridData(); + } + + private void setupGridData() { + mModelHelper.createGrid(new int[][][]{{ { APP_ICON, APP_ICON, SHORTCUT, SHORTCUT}, { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON}, { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT}, @@ -81,7 +101,7 @@ public class GridBackupTableTest extends BaseGridChangesTestCase { assertTrue(tableExists(mDb, BACKUP_TABLE_NAME)); - addItem(1, 2, DESKTOP, 1, 1); + mModelHelper.addItem(1, 2, DESKTOP, 1, 1); assertFalse(tableExists(mDb, BACKUP_TABLE_NAME)); } diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java index 53f6a06fdb..8dd7588c27 100644 --- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java @@ -1,25 +1,31 @@ package com.android.launcher3.model; import static com.android.launcher3.model.GridSizeMigrationTask.getWorkspaceScreenIds; +import static com.android.launcher3.util.LauncherModelHelper.APP_ICON; +import static com.android.launcher3.util.LauncherModelHelper.HOTSEAT; +import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT; +import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; import android.graphics.Point; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherSettings; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.FlagOverrideRule; import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.LauncherRoboTestRunner; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import java.util.HashSet; import java.util.LinkedList; @@ -27,30 +33,35 @@ import java.util.LinkedList; /** * Unit tests for {@link GridSizeMigrationTask} */ -@RunWith(RobolectricTestRunner.class) -public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase { +@RunWith(LauncherRoboTestRunner.class) +public class GridSizeMigrationTaskTest { - @Rule - public final FlagOverrideRule flags = new FlagOverrideRule(); + private LauncherModelHelper mModelHelper; + private Context mContext; + private SQLiteDatabase mDb; private HashSet mValidPackages; private InvariantDeviceProfile mIdp; @Before public void setUp() { + mModelHelper = new LauncherModelHelper(); + mContext = RuntimeEnvironment.application; + mDb = mModelHelper.provider.getDb(); + mValidPackages = new HashSet<>(); mValidPackages.add(TEST_PACKAGE); - mIdp = new InvariantDeviceProfile(); + mIdp = InvariantDeviceProfile.INSTANCE.get(mContext); } @Test public void testHotseatMigration_apps_dropped() throws Exception { int[] hotseatItems = { - addItem(APP_ICON, 0, HOTSEAT, 0, 0), - addItem(SHORTCUT, 1, HOTSEAT, 0, 0), + mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0), + mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0), -1, - addItem(SHORTCUT, 3, HOTSEAT, 0, 0), - addItem(APP_ICON, 4, HOTSEAT, 0, 0), + mModelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0), + mModelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0), }; mIdp.numHotseatIcons = 3; @@ -63,11 +74,11 @@ public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase { @Test public void testHotseatMigration_shortcuts_dropped() throws Exception { int[] hotseatItems = { - addItem(APP_ICON, 0, HOTSEAT, 0, 0), - addItem(30, 1, HOTSEAT, 0, 0), + mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0), + mModelHelper.addItem(30, 1, HOTSEAT, 0, 0), -1, - addItem(SHORTCUT, 3, HOTSEAT, 0, 0), - addItem(10, 4, HOTSEAT, 0, 0), + mModelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0), + mModelHelper.addItem(10, 4, HOTSEAT, 0, 0), }; mIdp.numHotseatIcons = 3; @@ -109,7 +120,7 @@ public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase { @Test public void testWorkspace_empty_row_column_removed() throws Exception { - int[][][] ids = createGrid(new int[][][]{{ + int[][][] ids = mModelHelper.createGrid(new int[][][]{{ { 0, 0, -1, 1}, { 3, 1, -1, 4}, { -1, -1, -1, -1}, @@ -129,7 +140,7 @@ public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase { @Test public void testWorkspace_new_screen_created() throws Exception { - int[][][] ids = createGrid(new int[][][]{{ + int[][][] ids = mModelHelper.createGrid(new int[][][]{{ { 0, 0, 0, 1}, { 3, 1, 0, 4}, { -1, -1, -1, -1}, @@ -151,7 +162,7 @@ public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase { @Test public void testWorkspace_items_merged_in_next_screen() throws Exception { - int[][][] ids = createGrid(new int[][][]{{ + int[][][] ids = mModelHelper.createGrid(new int[][][]{{ { 0, 0, 0, 1}, { 3, 1, 0, 4}, { -1, -1, -1, -1}, @@ -181,7 +192,7 @@ public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase { public void testWorkspace_items_not_merged_in_next_screen() throws Exception { // First screen has 2 items that need to be moved, but second screen has only one // empty space after migration (top-left corner) - int[][][] ids = createGrid(new int[][][]{{ + int[][][] ids = mModelHelper.createGrid(new int[][][]{{ { 0, 0, 0, 1}, { 3, 1, 0, 4}, { -1, -1, -1, -1}, @@ -217,7 +228,7 @@ public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase { } // The first screen has one item on the 4th column which needs moving, as the first row // will be kept empty. - int[][][] ids = createGrid(new int[][][]{{ + int[][][] ids = mModelHelper.createGrid(new int[][][]{{ { -1, -1, -1, -1}, { 3, 1, 7, 0}, { 8, 7, 7, -1}, @@ -244,7 +255,7 @@ public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase { return; } // Items will get moved to the next screen to keep the first screen empty. - int[][][] ids = createGrid(new int[][][]{{ + int[][][] ids = mModelHelper.createGrid(new int[][][]{{ { -1, -1, -1, -1}, { 0, 1, 0, 0}, { 8, 7, 7, -1}, diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java similarity index 76% rename from tests/src/com/android/launcher3/model/LoaderCursorTest.java rename to robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java index 0dcfaa814d..485431412e 100644 --- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2019 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.LauncherSettings.Favorites.CELLX; @@ -17,6 +33,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.RESTORED; import static com.android.launcher3.LauncherSettings.Favorites.SCREEN; import static com.android.launcher3.LauncherSettings.Favorites.TITLE; import static com.android.launcher3.LauncherSettings.Favorites._ID; +import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -24,43 +41,38 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.database.MatrixCursor; -import android.graphics.Bitmap; import android.os.Process; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.IconCache; +import com.android.launcher3.util.Executors; +import com.android.launcher3.util.LauncherRoboTestRunner; import com.android.launcher3.util.PackageManagerHelper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; /** * Tests for {@link LoaderCursor} */ -@SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(LauncherRoboTestRunner.class) +@LooperMode(Mode.PAUSED) public class LoaderCursorTest { - private LauncherAppState mMockApp; - private IconCache mMockIconCache; + private LauncherAppState mApp; private MatrixCursor mCursor; private InvariantDeviceProfile mIDP; @@ -71,22 +83,18 @@ public class LoaderCursorTest { @Before public void setup() { - mIDP = new InvariantDeviceProfile(); + mContext = RuntimeEnvironment.application; + mIDP = InvariantDeviceProfile.INSTANCE.get(mContext); + mApp = LauncherAppState.getInstance(mContext); + mLauncherApps = mContext.getSystemService(LauncherApps.class); + mCursor = new MatrixCursor(new String[] { ICON, ICON_PACKAGE, ICON_RESOURCE, TITLE, _ID, CONTAINER, ITEM_TYPE, PROFILE_ID, SCREEN, CELLX, CELLY, RESTORED, INTENT }); - mContext = InstrumentationRegistry.getTargetContext(); - mMockApp = mock(LauncherAppState.class); - mMockIconCache = mock(IconCache.class); - when(mMockApp.getIconCache()).thenReturn(mMockIconCache); - when(mMockApp.getInvariantDeviceProfile()).thenReturn(mIDP); - when(mMockApp.getContext()).thenReturn(mContext); - mLauncherApps = mContext.getSystemService(LauncherApps.class); - - mLoaderCursor = new LoaderCursor(mCursor, mMockApp); + mLoaderCursor = new LoaderCursor(mCursor, mApp); mLoaderCursor.allUsers.put(0, Process.myUserHandle()); } @@ -109,26 +117,31 @@ public class LoaderCursorTest { } @Test - public void getAppShortcutInfo_dontAllowMissing_validComponent() { + public void getAppShortcutInfo_dontAllowMissing_validComponent() throws Exception { + ComponentName cn = new ComponentName(TEST_PACKAGE, TEST_PACKAGE); + shadowOf(mContext.getPackageManager()).addActivityIfNotPresent(cn); + initCursor(ITEM_TYPE_APPLICATION, ""); assertTrue(mLoaderCursor.moveToNext()); - ComponentName cn = mLauncherApps.getActivityList(null, mLoaderCursor.user) - .get(0).getComponentName(); - WorkspaceItemInfo info = mLoaderCursor.getAppShortcutInfo( - new Intent().setComponent(cn), false /* allowMissingTarget */, true); + WorkspaceItemInfo info = Executors.MODEL_EXECUTOR.submit(() -> + mLoaderCursor.getAppShortcutInfo( + new Intent().setComponent(cn), false /* allowMissingTarget */, true)) + .get(); assertNotNull(info); assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent)); } @Test - public void getAppShortcutInfo_allowMissing_invalidComponent() { + public void getAppShortcutInfo_allowMissing_invalidComponent() throws Exception { initCursor(ITEM_TYPE_APPLICATION, ""); assertTrue(mLoaderCursor.moveToNext()); ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do"); - WorkspaceItemInfo info = mLoaderCursor.getAppShortcutInfo( - new Intent().setComponent(cn), true /* allowMissingTarget */, true); + WorkspaceItemInfo info = Executors.MODEL_EXECUTOR.submit(() -> + mLoaderCursor.getAppShortcutInfo( + new Intent().setComponent(cn), true /* allowMissingTarget */, true)) + .get(); assertNotNull(info); assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent)); } @@ -138,11 +151,8 @@ public class LoaderCursorTest { initCursor(ITEM_TYPE_SHORTCUT, "my-shortcut"); assertTrue(mLoaderCursor.moveToNext()); - Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); - when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user))) - .thenReturn(BitmapInfo.fromBitmap(icon)); WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem(); - assertEquals(icon, info.bitmap.icon); + assertTrue(mApp.getIconCache().isDefaultIcon(info.bitmap, info.user)); assertEquals("my-shortcut", info.title); assertEquals(ITEM_TYPE_SHORTCUT, info.itemType); } diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java index a1a456149f..bd71f01b6b 100644 --- a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java @@ -6,11 +6,14 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.pm.PackageInstallInfo; +import com.android.launcher3.util.LauncherModelHelper; +import com.android.launcher3.util.LauncherRoboTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; import java.util.Arrays; import java.util.HashSet; @@ -18,12 +21,16 @@ import java.util.HashSet; /** * Tests for {@link PackageInstallStateChangedTask} */ -@RunWith(RobolectricTestRunner.class) -public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase { +@RunWith(LauncherRoboTestRunner.class) +@LooperMode(Mode.PAUSED) +public class PackageInstallStateChangedTaskTest { + + private LauncherModelHelper mModelHelper; @Before - public void initData() throws Exception { - initializeData("/package_install_state_change_task_data.txt"); + public void setup() throws Exception { + mModelHelper = new LauncherModelHelper(); + mModelHelper.initializeData("/package_install_state_change_task_data.txt"); } private PackageInstallStateChangedTask newTask(String pkg, int progress) { @@ -35,7 +42,7 @@ public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestC @Test public void testSessionUpdate_ignore_installed() throws Exception { - executeTaskForTest(newTask("app1", 30)); + mModelHelper.executeTaskForTest(newTask("app1", 30)); // No shortcuts were updated verifyProgressUpdate(0); @@ -43,21 +50,21 @@ public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestC @Test public void testSessionUpdate_shortcuts_updated() throws Exception { - executeTaskForTest(newTask("app3", 30)); + mModelHelper.executeTaskForTest(newTask("app3", 30)); verifyProgressUpdate(30, 5, 6, 7); } @Test public void testSessionUpdate_widgets_updated() throws Exception { - executeTaskForTest(newTask("app4", 30)); + mModelHelper.executeTaskForTest(newTask("app4", 30)); verifyProgressUpdate(30, 8, 9); } private void verifyProgressUpdate(int progress, Integer... idsUpdated) { HashSet updates = new HashSet<>(Arrays.asList(idsUpdated)); - for (ItemInfo info : bgDataModel.itemsIdMap) { + for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) { if (info instanceof WorkspaceItemInfo) { assertEquals(updates.contains(info.id) ? progress: 0, ((WorkspaceItemInfo) info).getInstallProgress()); diff --git a/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java b/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java index 83bf7dac89..7612ae105a 100644 --- a/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java +++ b/robolectric_tests/src/com/android/launcher3/popup/PopupPopulatorTest.java @@ -27,9 +27,10 @@ import static org.mockito.Mockito.spy; import android.content.pm.ShortcutInfo; +import com.android.launcher3.util.LauncherRoboTestRunner; + import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; @@ -39,7 +40,7 @@ import java.util.List; /** * Tests the sorting and filtering of shortcuts in {@link PopupPopulator}. */ -@RunWith(RobolectricTestRunner.class) +@RunWith(LauncherRoboTestRunner.class) public class PopupPopulatorTest { @Test diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java similarity index 78% rename from tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java rename to robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java index 27990f4305..7ef670c270 100644 --- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java +++ b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2019 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.provider; import static org.junit.Assert.assertEquals; @@ -6,21 +21,18 @@ import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.MediumTest; -import androidx.test.runner.AndroidJUnit4; - import com.android.launcher3.LauncherProvider.DatabaseHelper; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.util.LauncherRoboTestRunner; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; /** * Tests for {@link RestoreDbTask} */ -@MediumTest -@RunWith(AndroidJUnit4.class) +@RunWith(LauncherRoboTestRunner.class) public class RestoreDbTaskTest { @Test @@ -83,7 +95,7 @@ public class RestoreDbTaskTest { private final long mProfileId; MyDatabaseHelper(long profileId) { - super(InstrumentationRegistry.getContext(), null); + super(RuntimeEnvironment.application, null); mProfileId = profileId; } diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java new file mode 100644 index 0000000000..696ffd0b54 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowAppWidgetManager.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 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.shadows; + +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.os.Process; +import android.os.UserHandle; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowAppWidgetManager; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Extension of {@link ShadowAppWidgetManager} with missing shadow methods + */ +@Implements(value = AppWidgetManager.class) +public class LShadowAppWidgetManager extends ShadowAppWidgetManager { + + @Override + protected List getInstalledProviders() { + return getInstalledProvidersForProfile(null); + } + + @Implementation + public List getInstalledProvidersForProfile(UserHandle profile) { + UserHandle user = profile == null ? Process.myUserHandle() : profile; + return super.getInstalledProviders().stream().filter( + info -> user.equals(info.getProfile())).collect(Collectors.toList()); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBitmap.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBitmap.java new file mode 100644 index 0000000000..abd90bb8f6 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBitmap.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 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.shadows; + +import android.graphics.Bitmap; +import android.graphics.Paint; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowBitmap; + +/** + * Extension of {@link ShadowBitmap} with missing shadow methods + */ +@Implements(value = Bitmap.class) +public class LShadowBitmap extends ShadowBitmap { + + @Implementation + protected Bitmap extractAlpha(Paint paint, int[] offsetXY) { + return extractAlpha(); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java index 204ec9bd17..ccbc18ad3c 100644 --- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java @@ -77,7 +77,7 @@ public class LShadowLauncherApps extends ShadowLauncherApps { protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) { ResolveInfo ri = RuntimeEnvironment.application.getPackageManager() .resolveActivity(intent, 0); - return getLauncherActivityInfo(ri.activityInfo); + return ri == null ? null : getLauncherActivityInfo(ri.activityInfo); } public LauncherActivityInfo getLauncherActivityInfo(ActivityInfo activityInfo) { diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java index d56de3c9cb..a3b7dc706f 100644 --- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java +++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java @@ -18,25 +18,16 @@ package com.android.launcher3.shadows; import static com.android.launcher3.util.Executors.createAndStartNewLooper; -import static org.robolectric.shadow.api.Shadow.invokeConstructor; -import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; +import static org.robolectric.shadow.api.Shadow.directlyOn; import static org.robolectric.util.ReflectionHelpers.setField; import android.os.Handler; -import android.os.Looper; -import com.android.launcher3.util.Executors; import com.android.launcher3.util.LooperExecutor; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; -import org.robolectric.util.ReflectionHelpers; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Set; -import java.util.WeakHashMap; /** * Shadow for {@link LooperExecutor} to provide reset functionality for static executors. @@ -44,25 +35,18 @@ import java.util.WeakHashMap; @Implements(value = LooperExecutor.class, isInAndroidSdk = false) public class ShadowLooperExecutor { - // Keep reference to all created Loopers so they can be torn down after test - private static Set executors = - Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); - - @RealObject private LooperExecutor realExecutor; + @RealObject private LooperExecutor mRealExecutor; @Implementation - protected void __constructor__(Looper looper) { - invokeConstructor(LooperExecutor.class, realExecutor, from(Looper.class, looper)); - executors.add(realExecutor); - } - - /** - * Re-initializes any executor which may have been reset when a test finished - */ - public static void reinitializeStaticExecutors() { - for (LooperExecutor executor : new ArrayList<>(executors)) { - setField(executor, "mHandler", - new Handler(createAndStartNewLooper(executor.getThread().getName()))); + protected Handler getHandler() { + Handler handler = directlyOn(mRealExecutor, LooperExecutor.class, "getHandler"); + Thread thread = handler.getLooper().getThread(); + if (!thread.isAlive()) { + // Robolectric destroys all loopers at the end of every test. Since Launcher maintains + // some static threads, they need to be reinitialized in case they were destroyed. + setField(mRealExecutor, "mHandler", + new Handler(createAndStartNewLooper(thread.getName()))); } + return directlyOn(mRealExecutor, LooperExecutor.class, "getHandler"); } } diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java new file mode 100644 index 0000000000..6e2ccf809f --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowMainThreadInitializedObject.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 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.shadows; + +import static org.robolectric.shadow.api.Shadow.invokeConstructor; +import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; + +import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.MainThreadInitializedObject.ObjectProvider; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * Shadow for {@link MainThreadInitializedObject} to provide reset functionality for static sObjects + */ +@Implements(value = MainThreadInitializedObject.class, isInAndroidSdk = false) +public class ShadowMainThreadInitializedObject { + + // Keep reference to all created MainThreadInitializedObject so they can be cleared after test + private static Set sObjects = + Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); + + @RealObject private MainThreadInitializedObject mRealObject; + + @Implementation + protected void __constructor__(ObjectProvider provider) { + invokeConstructor(MainThreadInitializedObject.class, mRealObject, + from(ObjectProvider.class, provider)); + sObjects.add(mRealObject); + } + + /** + * Resets all the initialized sObjects to be null + */ + public static void resetInitializedObjects() { + for (MainThreadInitializedObject object : new ArrayList<>(sObjects)) { + object.initializeForTesting(null); + } + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowTogglableFlag.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowTogglableFlag.java new file mode 100644 index 0000000000..3603dd834d --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowTogglableFlag.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 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.shadows; + +import android.content.Context; + +import com.android.launcher3.uioverrides.TogglableFlag; +import com.android.launcher3.util.LooperExecutor; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** + * Shadow for {@link LooperExecutor} to provide reset functionality for static executors. + */ +@Implements(value = TogglableFlag.class, isInAndroidSdk = false) +public class ShadowTogglableFlag { + + /** + * Mock change listener as it uses internal system classes not available to robolectric + */ + @Implementation + protected void addChangeListener(Context context, Runnable r) { } +} diff --git a/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java b/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java index aa51ad20c6..e453e31a41 100644 --- a/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java +++ b/robolectric_tests/src/com/android/launcher3/util/GridOccupancyTest.java @@ -2,7 +2,6 @@ package com.android.launcher3.util; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -11,7 +10,7 @@ import static org.junit.Assert.assertTrue; /** * Unit tests for {@link GridOccupancy} */ -@RunWith(RobolectricTestRunner.class) +@RunWith(LauncherRoboTestRunner.class) public class GridOccupancyTest { @Test diff --git a/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java index c08e198a8c..5974ea5db9 100644 --- a/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java +++ b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java @@ -19,12 +19,11 @@ import static com.google.common.truth.Truth.assertThat; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; /** * Robolectric unit tests for {@link IntArray} */ -@RunWith(RobolectricTestRunner.class) +@RunWith(LauncherRoboTestRunner.class) public class IntArrayTest { @Test diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java index 8513353dc6..aedf71ecbc 100644 --- a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java +++ b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java @@ -20,8 +20,6 @@ import static com.google.common.truth.Truth.assertThat; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -29,7 +27,7 @@ import static org.junit.Assert.assertTrue; /** * Robolectric unit tests for {@link IntSet} */ -@RunWith(RobolectricTestRunner.class) +@RunWith(LauncherRoboTestRunner.class) public class IntSetTest { @Test diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java new file mode 100644 index 0000000000..1a03f9f411 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2019 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 static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; + +import com.android.launcher3.AppInfo; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherModel.ModelUpdateTask; +import com.android.launcher3.LauncherProvider; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.model.AllAppsList; +import com.android.launcher3.model.BgDataModel; + +import org.mockito.ArgumentCaptor; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowContentResolver; +import org.robolectric.util.ReflectionHelpers; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Function; + +/** + * Utility class to help manage Launcher Model and related objects for test. + */ +public class LauncherModelHelper { + + public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP; + public static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT; + + public static final int APP_ICON = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + public static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; + public static final int NO__ICON = -1; + public static final String TEST_PACKAGE = "com.android.launcher3.validpackage"; + + private final HashMap> mFieldCache = new HashMap<>(); + public final TestLauncherProvider provider; + + private BgDataModel mDataModel; + private AllAppsList mAllAppsList; + + public LauncherModelHelper() { + provider = Robolectric.setupContentProvider(TestLauncherProvider.class); + ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, provider); + } + + public LauncherModel getModel() { + return LauncherAppState.getInstance(RuntimeEnvironment.application).getModel(); + } + + public synchronized BgDataModel getBgDataModel() { + if (mDataModel == null) { + mDataModel = ReflectionHelpers.getField(getModel(), "mBgDataModel"); + } + return mDataModel; + } + + public synchronized AllAppsList getAllAppsList() { + if (mAllAppsList == null) { + mAllAppsList = ReflectionHelpers.getField(getModel(), "mBgAllAppsList"); + } + return mAllAppsList; + } + + /** + * Synchronously executes the task and returns all the UI callbacks posted. + */ + public List executeTaskForTest(ModelUpdateTask task) throws Exception { + LauncherModel model = getModel(); + if (!model.isModelLoaded()) { + ReflectionHelpers.setField(model, "mModelLoaded", true); + } + Executor mockExecutor = mock(Executor.class); + model.enqueueModelUpdateTask(new ModelUpdateTask() { + @Override + public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel, + AllAppsList allAppsList, Executor uiExecutor) { + task.init(app, model, dataModel, allAppsList, mockExecutor); + } + + @Override + public void run() { + task.run(); + } + }); + MODEL_EXECUTOR.submit(() -> null).get(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + verify(mockExecutor, atLeast(0)).execute(captor.capture()); + return captor.getAllValues(); + } + + /** + * Synchronously executes a task on the model + */ + public T executeSimpleTask(Function task) throws Exception { + BgDataModel dataModel = getBgDataModel(); + return MODEL_EXECUTOR.submit(() -> task.apply(dataModel)).get(); + } + + /** + * Initializes mock data for the test. + */ + public void initializeData(String resourceName) throws Exception { + Context targetContext = RuntimeEnvironment.application; + BgDataModel bgDataModel = getBgDataModel(); + AllAppsList allAppsList = getAllAppsList(); + + MODEL_EXECUTOR.submit(() -> { + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + this.getClass().getResourceAsStream(resourceName)))) { + String line; + HashMap classMap = new HashMap<>(); + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.startsWith("#") || line.isEmpty()) { + continue; + } + String[] commands = line.split(" "); + switch (commands[0]) { + case "classMap": + classMap.put(commands[1], Class.forName(commands[2])); + break; + case "bgItem": + bgDataModel.addItem(targetContext, + (ItemInfo) initItem(classMap.get(commands[1]), commands, 2), + false); + break; + case "allApps": + allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null); + break; + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }).get(); + } + + private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception { + HashMap cache = mFieldCache.get(clazz); + if (cache == null) { + cache = new HashMap<>(); + Class c = clazz; + while (c != null) { + for (Field f : c.getDeclaredFields()) { + f.setAccessible(true); + cache.put(f.getName(), f); + } + c = c.getSuperclass(); + } + mFieldCache.put(clazz, cache); + } + + Object item = clazz.newInstance(); + for (int i = startIndex; i < fieldDef.length; i++) { + String[] fieldData = fieldDef[i].split("=", 2); + Field f = cache.get(fieldData[0]); + Class type = f.getType(); + if (type == int.class || type == long.class) { + f.set(item, Integer.parseInt(fieldData[1])); + } else if (type == CharSequence.class || type == String.class) { + f.set(item, fieldData[1]); + } else if (type == Intent.class) { + if (!fieldData[1].startsWith("#Intent")) { + fieldData[1] = "#Intent;" + fieldData[1] + ";end"; + } + f.set(item, Intent.parseUri(fieldData[1], 0)); + } else if (type == ComponentName.class) { + f.set(item, ComponentName.unflattenFromString(fieldData[1])); + } else { + throw new Exception("Added parsing logic for " + + f.getName() + " of type " + f.getType()); + } + } + return item; + } + + /** + * Adds a dummy item in the DB. + * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for + * folder (where the type represents the number of items in the folder). + */ + public int addItem(int type, int screen, int container, int x, int y) { + Context context = RuntimeEnvironment.application; + int id = LauncherSettings.Settings.call(context.getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_ITEM_ID) + .getInt(LauncherSettings.Settings.EXTRA_VALUE); + + ContentValues values = new ContentValues(); + values.put(LauncherSettings.Favorites._ID, id); + values.put(LauncherSettings.Favorites.CONTAINER, container); + values.put(LauncherSettings.Favorites.SCREEN, screen); + values.put(LauncherSettings.Favorites.CELLX, x); + values.put(LauncherSettings.Favorites.CELLY, y); + values.put(LauncherSettings.Favorites.SPANX, 1); + values.put(LauncherSettings.Favorites.SPANY, 1); + + if (type == APP_ICON || type == SHORTCUT) { + values.put(LauncherSettings.Favorites.ITEM_TYPE, type); + values.put(LauncherSettings.Favorites.INTENT, + new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0)); + } else { + values.put(LauncherSettings.Favorites.ITEM_TYPE, + LauncherSettings.Favorites.ITEM_TYPE_FOLDER); + // Add folder items. + for (int i = 0; i < type; i++) { + addItem(APP_ICON, 0, id, 0, 0); + } + } + + context.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values); + return id; + } + + public int[][][] createGrid(int[][][] typeArray) { + return createGrid(typeArray, 1); + } + + /** + * Initializes the DB with dummy elements to represent the provided grid structure. + * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for + * type definitions. The first dimension represents the screens and the next + * two represent the workspace grid. + * @param startScreen First screen id from where the icons will be added. + * @return the same grid representation where each entry is the corresponding item id. + */ + public int[][][] createGrid(int[][][] typeArray, int startScreen) { + Context context = RuntimeEnvironment.application; + LauncherSettings.Settings.call(context.getContentResolver(), + LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); + int[][][] ids = new int[typeArray.length][][]; + + for (int i = 0; i < typeArray.length; i++) { + // Add screen to DB + int screenId = startScreen + i; + + // Keep the screen id counter up to date + LauncherSettings.Settings.call(context.getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); + + ids[i] = new int[typeArray[i].length][]; + for (int y = 0; y < typeArray[i].length; y++) { + ids[i][y] = new int[typeArray[i][y].length]; + for (int x = 0; x < typeArray[i][y].length; x++) { + if (typeArray[i][y][x] < 0) { + // Empty cell + ids[i][y][x] = -1; + } else { + ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y); + } + } + } + } + + return ids; + } +} diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java new file mode 100644 index 0000000000..5c6b486ea5 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 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 static org.mockito.Mockito.mock; + +import com.android.launcher3.shadows.LShadowAppWidgetManager; +import com.android.launcher3.shadows.LShadowBitmap; +import com.android.launcher3.shadows.LShadowLauncherApps; +import com.android.launcher3.shadows.LShadowUserManager; +import com.android.launcher3.shadows.ShadowLooperExecutor; +import com.android.launcher3.shadows.ShadowMainThreadInitializedObject; +import com.android.launcher3.shadows.ShadowTogglableFlag; +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; + +import org.junit.runners.model.InitializationError; +import org.robolectric.DefaultTestLifecycle; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.TestLifecycle; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLog; + +import java.lang.reflect.Method; + +import javax.annotation.Nonnull; + +/** + * Test runner with Launcher specific configurations + */ +public class LauncherRoboTestRunner extends RobolectricTestRunner { + + private static final Class[] SHADOWS = new Class[] { + LShadowAppWidgetManager.class, + LShadowUserManager.class, + LShadowLauncherApps.class, + LShadowBitmap.class, + + ShadowLooperExecutor.class, + ShadowMainThreadInitializedObject.class, + ShadowTogglableFlag.class, + }; + + public LauncherRoboTestRunner(Class testClass) throws InitializationError { + super(testClass); + } + + @Override + protected Config buildGlobalConfig() { + return new Config.Builder().setShadows(SHADOWS).build(); + } + + @Nonnull + @Override + protected Class getTestLifecycleClass() { + return LauncherTestLifecycle.class; + } + + public static class LauncherTestLifecycle extends DefaultTestLifecycle { + + @Override + public void beforeTest(Method method) { + super.beforeTest(method); + ShadowLog.stream = System.out; + + // Disable plugins + PluginManagerWrapper.INSTANCE.initializeForTesting(mock(PluginManagerWrapper.class)); + } + + @Override + public void afterTest(Method method) { + super.afterTest(method); + + ShadowLog.stream = null; + ShadowMainThreadInitializedObject.resetInitializedObjects(); + } + } +} diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java similarity index 84% rename from tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java rename to robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java index 57b0b09e25..daae8186a6 100644 --- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java @@ -19,16 +19,15 @@ import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.robolectric.Shadows.shadowOf; import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; import android.content.Context; import android.graphics.Bitmap; import android.view.LayoutInflater; import androidx.recyclerview.widget.RecyclerView; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppWidgetProviderInfo; @@ -37,19 +36,21 @@ import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.model.WidgetItem; -import com.android.launcher3.util.MultiHashMap; +import com.android.launcher3.util.LauncherRoboTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowPackageManager; +import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; -import java.util.Map; +import java.util.Collections; -@SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(LauncherRoboTestRunner.class) public class WidgetsListAdapterTest { @Mock private LayoutInflater mMockLayoutInflater; @@ -64,7 +65,7 @@ public class WidgetsListAdapterTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - mContext = InstrumentationRegistry.getTargetContext(); + mContext = RuntimeEnvironment.application; mTestProfile = new InvariantDeviceProfile(); mTestProfile.numRows = 5; mTestProfile.numColumns = 5; @@ -121,15 +122,19 @@ public class WidgetsListAdapterTest { /** * Helper method to generate the sample widget model map that can be used for the tests * @param num the number of WidgetItem the map should contain - * @return */ private ArrayList generateSampleMap(int num) { ArrayList result = new ArrayList<>(); if (num <= 0) return result; + ShadowPackageManager spm = shadowOf(mContext.getPackageManager()); + + for (int i = 0; i < num; i++) { + ComponentName cn = new ComponentName("com.dummy.apk" + i, "DummyWidet"); + + AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo(); + widgetInfo.provider = cn; + ReflectionHelpers.setField(widgetInfo, "providerInfo", spm.addReceiverIfNotPresent(cn)); - MultiHashMap newMap = new MultiHashMap(); - WidgetManagerHelper widgetManager = new WidgetManagerHelper(mContext); - for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) { WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo .fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache); @@ -137,13 +142,8 @@ public class WidgetsListAdapterTest { pInfo.title = pInfo.packageName; pInfo.user = wi.user; pInfo.bitmap = BitmapInfo.of(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), 0); - newMap.addToList(pInfo, wi); - if (newMap.size() == num) { - break; - } - } - for (Map.Entry> entry : newMap.entrySet()) { - result.add(new WidgetListRowEntry(entry.getKey(), entry.getValue())); + + result.add(new WidgetListRowEntry(pInfo, new ArrayList<>(Collections.singleton(wi)))); } return result; diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 0bdf8fdd90..e00532003f 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -108,14 +108,14 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi * All the static data should be accessed on the background thread, A lock should be acquired * on this object when accessing any data from this model. */ - static final BgDataModel sBgDataModel = new BgDataModel(); + private final BgDataModel mBgDataModel = new BgDataModel(); // Runnable to check if the shortcuts permission has changed. private final Runnable mShortcutPermissionCheckRunnable = new Runnable() { @Override public void run() { if (mModelLoaded && hasShortcutsPermission(mApp.getContext()) - != sBgDataModel.hasShortcutHostPermission) { + != mBgDataModel.hasShortcutHostPermission) { forceReload(); } } @@ -138,7 +138,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi } public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) { - return new ModelWriter(mApp.getContext(), this, sBgDataModel, + return new ModelWriter(mApp.getContext(), this, mBgDataModel, hasVerticalHotseat, verifyChanges); } @@ -303,7 +303,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi // If there is already one running, tell it to stop. stopLoader(); - LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel, + LoaderResults loaderResults = new LoaderResults(mApp, mBgDataModel, mBgAllAppsList, synchronousBindPage, mCallbacks); if (mModelLoaded && !mIsLoaderTaskRunning) { // Divide the set of loaded items into those that we are binding synchronously, @@ -339,7 +339,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi public void startLoaderForResults(LoaderResults results) { synchronized (mLock) { stopLoader(); - mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results); + mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results); // Always post the loader task, instead of running directly (even on same thread) so // that we exit any nested synchronized blocks @@ -487,7 +487,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi } public void enqueueModelUpdateTask(ModelUpdateTask task) { - task.init(mApp, this, sBgDataModel, mBgAllAppsList, MAIN_EXECUTOR); + task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR); MODEL_EXECUTOR.execute(task); } @@ -558,7 +558,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi + " componentName=" + info.componentName.getPackageName()); } } - sBgDataModel.dump(prefix, fd, writer, args); + mBgDataModel.dump(prefix, fd, writer, args); } public Callbacks getCallback() { diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java index 4d5ee49e83..0a32734fca 100644 --- a/src/com/android/launcher3/util/Executors.java +++ b/src/com/android/launcher3/util/Executors.java @@ -19,7 +19,6 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Process; -import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -36,9 +35,9 @@ public class Executors { private static final int KEEP_ALIVE = 1; /** - * An {@link Executor} to be used with async task with no limit on the queue size. + * An {@link ThreadPoolExecutor} to be used with async task with no limit on the queue size. */ - public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( + public static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java index 8ac600f735..3a8a13c8bb 100644 --- a/src/com/android/launcher3/util/LooperExecutor.java +++ b/src/com/android/launcher3/util/LooperExecutor.java @@ -41,10 +41,10 @@ public class LooperExecutor extends AbstractExecutorService { @Override public void execute(Runnable runnable) { - if (mHandler.getLooper() == Looper.myLooper()) { + if (getHandler().getLooper() == Looper.myLooper()) { runnable.run(); } else { - mHandler.post(runnable); + getHandler().post(runnable); } } @@ -52,7 +52,7 @@ public class LooperExecutor extends AbstractExecutorService { * Same as execute, but never runs the action inline. */ public void post(Runnable runnable) { - mHandler.post(runnable); + getHandler().post(runnable); } /** @@ -96,14 +96,14 @@ public class LooperExecutor extends AbstractExecutorService { * Returns the thread for this executor */ public Thread getThread() { - return mHandler.getLooper().getThread(); + return getHandler().getLooper().getThread(); } /** * Returns the looper for this executor */ public Looper getLooper() { - return mHandler.getLooper(); + return getHandler().getLooper(); } /**