summaryrefslogtreecommitdiffstats
path: root/robolectric_tests
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2018-11-07 16:54:02 -0800
committerSunny Goyal <sunnygoyal@google.com>2018-11-12 10:49:59 -0800
commit06a07e9748d07941be5f49f3388e94e5fcdeb840 (patch)
treee83cb6d85badbe22ef33e225e4d883932e25da1f /robolectric_tests
parentf1982fcaae1c24c93eab2309cb06905d25a90c00 (diff)
downloadandroid_packages_apps_Trebuchet-06a07e9748d07941be5f49f3388e94e5fcdeb840.tar.gz
android_packages_apps_Trebuchet-06a07e9748d07941be5f49f3388e94e5fcdeb840.tar.bz2
android_packages_apps_Trebuchet-06a07e9748d07941be5f49f3388e94e5fcdeb840.zip
Moving come tests to Roboelectric
> Fixing resource loading in robo tests Change-Id: Id5b8a0e4916a2a200da7a41b03f19846834beb1f
Diffstat (limited to 'robolectric_tests')
-rw-r--r--robolectric_tests/Android.mk2
-rw-r--r--robolectric_tests/config/robolectric.properties2
-rw-r--r--robolectric_tests/resources/cache_data_updated_task_data.txt28
-rw-r--r--robolectric_tests/resources/package_install_state_change_task_data.txt24
-rw-r--r--robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java191
-rw-r--r--robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java222
-rw-r--r--robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java95
-rw-r--r--robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java463
-rw-r--r--robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java70
-rw-r--r--robolectric_tests/src/com/android/launcher3/util/IntSetTest.java2
-rw-r--r--robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java46
11 files changed, 1143 insertions, 2 deletions
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index 501176475..a57b97a9c 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -29,6 +29,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
LOCAL_JAVA_LIBRARIES := \
platform-robolectric-3.6.1-prebuilt
+LOCAL_JAVA_RESOURCE_DIRS := resources config
+
LOCAL_INSTRUMENTATION_FOR := Launcher3
LOCAL_MODULE_TAGS := optional
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
new file mode 100644
index 000000000..782b8cb75
--- /dev/null
+++ b/robolectric_tests/config/robolectric.properties
@@ -0,0 +1,2 @@
+manifest=packages/apps/Launcher3/AndroidManifest.xml
+sdk=28 \ No newline at end of file
diff --git a/robolectric_tests/resources/cache_data_updated_task_data.txt b/robolectric_tests/resources/cache_data_updated_task_data.txt
new file mode 100644
index 000000000..819968709
--- /dev/null
+++ b/robolectric_tests/resources/cache_data_updated_task_data.txt
@@ -0,0 +1,28 @@
+# Model data used by CacheDataUpdatedTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Auto install app shortcut
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+
+# Custom shortcuts
+bgItem s itemType=1 title=app1-shrt intent=component=app1/class3 id=7
+bgItem s itemType=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Restored custom shortcut
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=9
+bgItem s itemType=1 status=1 title=app5-shrt intent=component=app5/class1 id=10
+
+allApps componentName=app1/class1 intent=component=app1/class1
+allApps componentName=app1/class2 intent=component=app1/class2
+allApps componentName=app2/class1 intent=component=app2/class1
+allApps componentName=app2/class2 intent=component=app2/class2 \ No newline at end of file
diff --git a/robolectric_tests/resources/package_install_state_change_task_data.txt b/robolectric_tests/resources/package_install_state_change_task_data.txt
new file mode 100644
index 000000000..84f9c161e
--- /dev/null
+++ b/robolectric_tests/resources/package_install_state_change_task_data.txt
@@ -0,0 +1,24 @@
+# Model data used by PackageInstallStateChangeTaskTest
+
+classMap s com.android.launcher3.ShortcutInfo
+classMap w com.android.launcher3.LauncherAppWidgetInfo
+
+# Items for the BgDataModel
+
+# App shortcuts
+bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
+bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
+bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
+bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
+
+# Promise icons for app3
+bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
+bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
+bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
+
+# Promise icon for app4
+bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
+
+# Widget
+bgItem w providerName=app4/provider1 id=9
+bgItem w providerName=app5/provider1 id=10 \ No newline at end of file
diff --git a/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
new file mode 100644
index 000000000..fd31033db
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -0,0 +1,191 @@
+package com.android.launcher3.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.util.Pair;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSparseArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link AddWorkspaceItemsTask}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
+
+ private final ComponentName mComponent1 = new ComponentName("a", "b");
+ private final ComponentName mComponent2 = new ComponentName("b", "b");
+
+ private IntArray existingScreens;
+ private IntArray newScreens;
+ private IntSparseArrayMap<GridOccupancy> screenOccupancy;
+
+ @Before
+ public void initData() throws Exception {
+ existingScreens = new IntArray();
+ screenOccupancy = new IntSparseArrayMap<>();
+ newScreens = new IntArray();
+
+ idp.numColumns = 5;
+ idp.numRows = 5;
+ }
+
+ private AddWorkspaceItemsTask newTask(ItemInfo... items) {
+ List<Pair<ItemInfo, Object>> list = new ArrayList<>();
+ for (ItemInfo item : items) {
+ list.add(Pair.create(item, null));
+ }
+ return new AddWorkspaceItemsTask(list) {
+
+ @Override
+ protected void updateScreens(Context context, IntArray workspaceScreens) { }
+ };
+ }
+
+ @Test
+ public void testFindSpaceForItem_prefers_second() {
+ // First screen has only one hole of size 1
+ int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+
+ // Second screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+
+ int[] spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 1, 1);
+ assertEquals(2, spaceFound[0]);
+ assertTrue(screenOccupancy.get(spaceFound[0])
+ .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
+
+ // Find a larger space
+ spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3);
+ assertEquals(2, spaceFound[0]);
+ assertTrue(screenOccupancy.get(spaceFound[0])
+ .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3));
+ }
+
+ @Test
+ public void testFindSpaceForItem_adds_new_screen() throws Exception {
+ // First screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+ commitScreensToDb();
+
+ IntArray oldScreens = existingScreens.clone();
+ int[] spaceFound = newTask()
+ .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
+ assertFalse(oldScreens.contains(spaceFound[0]));
+ assertTrue(newScreens.contains(spaceFound[0]));
+ }
+
+ @Test
+ public void testAddItem_existing_item_ignored() throws Exception {
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+ commitScreensToDb();
+
+ // Nothing was added
+ assertTrue(executeTaskForTest(newTask(info)).isEmpty());
+ }
+
+ @Test
+ public void testAddItem_some_items_added() throws Exception {
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ ShortcutInfo info2 = new ShortcutInfo();
+ info2.intent = new Intent().setComponent(mComponent2);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+ commitScreensToDb();
+
+ executeTaskForTest(newTask(info, info2)).get(0).run();
+ ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
+ ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
+
+ // only info2 should be added because info was already added to the workspace
+ // in setupWorkspaceWithHoles()
+ verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(),
+ animated.capture());
+ assertTrue(notAnimated.getValue().isEmpty());
+
+ assertEquals(1, animated.getValue().size());
+ assertTrue(animated.getValue().contains(info2));
+ }
+
+ private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) {
+ GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows);
+ occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true);
+ for (Rect r : holes) {
+ occupancy.markCells(r, false);
+ }
+
+ existingScreens.add(screenId);
+ screenOccupancy.append(screenId, occupancy);
+
+ for (int x = 0; x < idp.numColumns; x++) {
+ for (int y = 0; y < idp.numRows; y++) {
+ if (!occupancy.cells[x][y]) {
+ continue;
+ }
+
+ ShortcutInfo info = new ShortcutInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+ info.id = startId++;
+ info.screenId = screenId;
+ info.cellX = x;
+ info.cellY = y;
+ info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ bgDataModel.addItem(targetContext, info, false);
+ }
+ }
+ return startId;
+ }
+
+ private void commitScreensToDb() throws Exception {
+ LauncherSettings.Settings.call(targetContext.getContentResolver(),
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+
+ Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
+ ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+ // Clear the table
+ ops.add(ContentProviderOperation.newDelete(uri).build());
+ int count = existingScreens.size();
+ for (int i = 0; i < count; i++) {
+ ContentValues v = new ContentValues();
+ int screenId = existingScreens.get(i);
+ v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
+ v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
+ ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
+ }
+ targetContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
new file mode 100644
index 000000000..92d065e30
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java
@@ -0,0 +1,222 @@
+package com.android.launcher3.model;
+
+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 com.android.launcher3.AllAppsList;
+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.Callbacks;
+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.util.ComponentKey;
+import com.android.launcher3.util.Provider;
+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 androidx.annotation.NonNull;
+
+/**
+ * Base class for writing tests for Model update tasks.
+ */
+public class BaseModelUpdateTaskTestCase {
+
+ public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
+ private TestLauncherProvider mProvider;
+
+ 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;
+
+ mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
+ ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
+
+ callbacks = mock(Callbacks.class);
+ appState = mock(LauncherAppState.class);
+ model = mock(LauncherModel.class);
+ modelWriter = mock(ModelWriter.class);
+
+ 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<Runnable> 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<Runnable> 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<String, Class> 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<String, Field> 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<ComponentKey, CacheEntry> mCache = new HashMap<>();
+
+ public MyIconCache(Context context, InvariantDeviceProfile idp) {
+ super(context, idp);
+ }
+
+ @Override
+ protected <T> CacheEntry cacheLocked(
+ @NonNull ComponentName componentName,
+ UserHandle user, @NonNull Provider<T> infoProvider,
+ @NonNull CachingLogic<T> cachingLogic,
+ boolean usePackageIcon, boolean useLowResIcon) {
+ CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
+ if (entry == null) {
+ entry = new CacheEntry();
+ getDefaultIcon(user).applyTo(entry);
+ }
+ return entry;
+ }
+
+ public void addCache(ComponentName key, String title) {
+ CacheEntry entry = new CacheEntry();
+ entry.icon = newIcon();
+ entry.color = Color.RED;
+ entry.title = title;
+ mCache.put(new ComponentKey(key, Process.myUserHandle()), entry);
+ }
+
+ public Bitmap newIcon() {
+ return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+ }
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
new file mode 100644
index 000000000..f17eac740
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -0,0 +1,95 @@
+package com.android.launcher3.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ShortcutInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * Tests for {@link CacheDataUpdatedTask}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
+
+ private static final String NEW_LABEL_PREFIX = "new-label-";
+
+ @Before
+ public void initData() throws Exception {
+ 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);
+ }
+ }
+
+ private CacheDataUpdatedTask newTask(int op, String... pkg) {
+ return new CacheDataUpdatedTask(op, myUser, 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) {
+ info.iconBitmap = null;
+ }
+
+ 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) {
+ if (info.componentName.getPackageName().equals("app1")) {
+ assertNotNull(info.iconBitmap);
+ } else {
+ assertNull(info.iconBitmap);
+ }
+ }
+ }
+
+ @Test
+ public void testSessionUpdate_ignores_normal_apps() throws Exception {
+ executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
+
+ // app1 has no restored shortcuts. Verify that nothing was updated.
+ verifyUpdate();
+ }
+
+ @Test
+ public void testSessionUpdate_updates_pending_apps() throws Exception {
+ 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
+ verifyUpdate(5, 6);
+ }
+
+ private void verifyUpdate(Integer... idsUpdated) {
+ HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ if (updates.contains(info.id)) {
+ assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
+ assertNotNull(((ShortcutInfo) info).iconBitmap);
+ } else {
+ assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
+ assertNull(((ShortcutInfo) info).iconBitmap);
+ }
+ }
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
new file mode 100644
index 000000000..67580ddd8
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
@@ -0,0 +1,463 @@
+package com.android.launcher3.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Point;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FlagOverrideRule;
+import com.android.launcher3.config.FlagOverrideRule.FlagOverride;
+import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.TestLauncherProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowContentResolver;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+
+/**
+ * Unit tests for {@link GridSizeMigrationTask}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class GridSizeMigrationTaskTest {
+
+ private static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ private static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+
+ private static final int APPLICATION = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+ private static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+
+ private static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
+
+ @Rule
+ public final FlagOverrideRule flags = new FlagOverrideRule();
+
+ private HashSet<String> mValidPackages;
+ private InvariantDeviceProfile mIdp;
+ private Context mContext;
+ private TestLauncherProvider mProvider;
+
+ @Before
+ public void setUp() {
+
+ mValidPackages = new HashSet<>();
+ mValidPackages.add(TEST_PACKAGE);
+ mIdp = new InvariantDeviceProfile();
+ mContext = RuntimeEnvironment.application;
+
+ mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
+ ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
+ }
+
+ @Test
+ public void testHotseatMigration_apps_dropped() throws Exception {
+ int[] hotseatItems = {
+ addItem(APPLICATION, 0, HOTSEAT, 0, 0),
+ addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
+ -1,
+ addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
+ addItem(APPLICATION, 4, HOTSEAT, 0, 0),
+ };
+
+ mIdp.numHotseatIcons = 3;
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
+ .migrateHotseat();
+ // First item is dropped as it has the least weight.
+ verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
+ }
+
+ @Test
+ public void testHotseatMigration_shortcuts_dropped() throws Exception {
+ int[] hotseatItems = {
+ addItem(APPLICATION, 0, HOTSEAT, 0, 0),
+ addItem(30, 1, HOTSEAT, 0, 0),
+ -1,
+ addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
+ addItem(10, 4, HOTSEAT, 0, 0),
+ };
+
+ mIdp.numHotseatIcons = 3;
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
+ .migrateHotseat();
+ // First item is dropped as it has the least weight.
+ verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
+ }
+
+ private void verifyHotseat(int... sortedIds) {
+ int screenId = 0;
+ int total = 0;
+
+ for (int id : sortedIds) {
+ Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[]{LauncherSettings.Favorites._ID},
+ "container=-101 and screen=" + screenId, null, null, null);
+
+ if (id == -1) {
+ assertEquals(0, c.getCount());
+ } else {
+ assertEquals(1, c.getCount());
+ c.moveToNext();
+ assertEquals(id, c.getLong(0));
+ total ++;
+ }
+ c.close();
+
+ screenId++;
+ }
+
+ // Verify that not other entry exist in the DB.
+ Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[]{LauncherSettings.Favorites._ID},
+ "container=-101", null, null, null);
+ assertEquals(total, c.getCount());
+ c.close();
+ }
+
+ @Test
+ public void testWorkspace_empty_row_column_removed() throws Exception {
+ int[][][] ids = createGrid(new int[][][]{{
+ { 0, 0, -1, 1},
+ { 3, 1, -1, 4},
+ { -1, -1, -1, -1},
+ { 5, 2, -1, 6},
+ }});
+
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new Point(4, 4), new Point(3, 3)).migrateWorkspace();
+
+ // Column 2 and row 2 got removed.
+ verifyWorkspace(new int[][][] {{
+ {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
+ {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
+ {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
+ }});
+ }
+
+ @Test
+ public void testWorkspace_new_screen_created() throws Exception {
+ int[][][] ids = createGrid(new int[][][]{{
+ { 0, 0, 0, 1},
+ { 3, 1, 0, 4},
+ { -1, -1, -1, -1},
+ { 5, 2, -1, 6},
+ }});
+
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new Point(4, 4), new Point(3, 3)).migrateWorkspace();
+
+ // Items in the second column get moved to new screen
+ verifyWorkspace(new int[][][] {{
+ {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
+ {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
+ {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
+ }, {
+ {ids[0][0][2], ids[0][1][2], -1},
+ }});
+ }
+
+ @Test
+ public void testWorkspace_items_merged_in_next_screen() throws Exception {
+ int[][][] ids = createGrid(new int[][][]{{
+ { 0, 0, 0, 1},
+ { 3, 1, 0, 4},
+ { -1, -1, -1, -1},
+ { 5, 2, -1, 6},
+ },{
+ { 0, 0, -1, 1},
+ { 3, 1, -1, 4},
+ }});
+
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new Point(4, 4), new Point(3, 3)).migrateWorkspace();
+
+ // Items in the second column of the first screen should get placed on the 3rd
+ // row of the second screen
+ verifyWorkspace(new int[][][] {{
+ {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
+ {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
+ {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
+ }, {
+ {ids[1][0][0], ids[1][0][1], ids[1][0][3]},
+ {ids[1][1][0], ids[1][1][1], ids[1][1][3]},
+ {ids[0][0][2], ids[0][1][2], -1},
+ }});
+ }
+
+ @Test
+ 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[][][]{{
+ { 0, 0, 0, 1},
+ { 3, 1, 0, 4},
+ { -1, -1, -1, -1},
+ { 5, 2, -1, 6},
+ },{
+ { -1, 0, -1, 1},
+ { 3, 1, -1, 4},
+ { -1, -1, -1, -1},
+ { 5, 2, -1, 6},
+ }});
+
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new Point(4, 4), new Point(3, 3)).migrateWorkspace();
+
+ // Items in the second column of the first screen should get placed on a new screen.
+ verifyWorkspace(new int[][][] {{
+ {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
+ {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
+ {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
+ }, {
+ { -1, ids[1][0][1], ids[1][0][3]},
+ {ids[1][1][0], ids[1][1][1], ids[1][1][3]},
+ {ids[1][3][0], ids[1][3][1], ids[1][3][3]},
+ }, {
+ {ids[0][0][2], ids[0][1][2], -1},
+ }});
+ }
+
+ @FlagOverride(key = "QSB_ON_FIRST_SCREEN", value = true)
+ @Test
+ public void testWorkspace_first_row_blocked() throws Exception {
+ // 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[][][]{{
+ { -1, -1, -1, -1},
+ { 3, 1, 7, 0},
+ { 8, 7, 7, -1},
+ { 5, 2, 7, -1},
+ }}, 0);
+
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new Point(4, 4), new Point(3, 4)).migrateWorkspace();
+
+ // Items in the second column of the first screen should get placed on a new screen.
+ verifyWorkspace(new int[][][] {{
+ { -1, -1, -1},
+ {ids[0][1][0], ids[0][1][1], ids[0][1][2]},
+ {ids[0][2][0], ids[0][2][1], ids[0][2][2]},
+ {ids[0][3][0], ids[0][3][1], ids[0][3][2]},
+ }, {
+ {ids[0][1][3]},
+ }});
+ }
+
+ @FlagOverride(key = "QSB_ON_FIRST_SCREEN", value = true)
+ @Test
+ public void testWorkspace_items_moved_to_empty_first_row() throws Exception {
+ // Items will get moved to the next screen to keep the first screen empty.
+ int[][][] ids = createGrid(new int[][][]{{
+ { -1, -1, -1, -1},
+ { 0, 1, 0, 0},
+ { 8, 7, 7, -1},
+ { 5, 6, 7, -1},
+ }}, 0);
+
+ new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
+ new Point(4, 4), new Point(3, 3)).migrateWorkspace();
+
+ // Items in the second column of the first screen should get placed on a new screen.
+ verifyWorkspace(new int[][][] {{
+ { -1, -1, -1},
+ {ids[0][2][0], ids[0][2][1], ids[0][2][2]},
+ {ids[0][3][0], ids[0][3][1], ids[0][3][2]},
+ }, {
+ {ids[0][1][1], ids[0][1][0], ids[0][1][2]},
+ {ids[0][1][3]},
+ }});
+ }
+
+ private int[][][] createGrid(int[][][] typeArray) throws Exception {
+ 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.
+ * @return the same grid representation where each entry is the corresponding item id.
+ */
+ private int[][][] createGrid(int[][][] typeArray, int startScreen) throws Exception {
+ 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);
+
+ ContentValues v = new ContentValues();
+ v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
+ v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
+ mContext.getContentResolver().insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
+
+ 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);
+ }
+ }
+ }
+ }
+
+ IntArray allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
+ return ids;
+ }
+
+ /**
+ * Verifies that the workspace items are arranged in the provided order.
+ * @param ids A 3d array where the first dimension represents the screen, and the rest two
+ * represent the workspace grid.
+ */
+ private void verifyWorkspace(int[][][] ids) {
+ IntArray allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
+ assertEquals(ids.length, allScreens.size());
+ int total = 0;
+
+ for (int i = 0; i < ids.length; i++) {
+ int screenId = allScreens.get(i);
+ for (int y = 0; y < ids[i].length; y++) {
+ for (int x = 0; x < ids[i][y].length; x++) {
+ int id = ids[i][y][x];
+
+ Cursor c = mContext.getContentResolver().query(
+ LauncherSettings.Favorites.CONTENT_URI,
+ new String[]{LauncherSettings.Favorites._ID},
+ "container=-100 and screen=" + screenId +
+ " and cellX=" + x + " and cellY=" + y, null, null, null);
+ if (id == -1) {
+ assertEquals(0, c.getCount());
+ } else {
+ assertEquals(1, c.getCount());
+ c.moveToNext();
+ assertEquals(String.format("Failed to verify item at %d %d, %d", i, y, x),
+ id, c.getLong(0));
+ total++;
+ }
+ c.close();
+ }
+ }
+ }
+
+ // Verify that not other entry exist in the DB.
+ Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[]{LauncherSettings.Favorites._ID},
+ "container=-100", null, null, null);
+ assertEquals(total, c.getCount());
+ c.close();
+ }
+
+ /**
+ * Adds a dummy item in the DB.
+ * @param type {@link #APPLICATION} or {@link #SHORTCUT} or >= 2 for
+ * folder (where the type represents the number of items in the folder).
+ */
+ private int addItem(int type, int screen, int container, int x, int y) throws Exception {
+ 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 == APPLICATION || 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(APPLICATION, 0, id, 0, 0);
+ }
+ }
+
+ mContext.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
+ return id;
+ }
+
+ @Test
+ public void testMultiStepMigration_small_to_large() throws Exception {
+ MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
+ verifier.migrate(new Point(3, 3), new Point(5, 5));
+ verifier.assertCompleted();
+ }
+
+ @Test
+ public void testMultiStepMigration_large_to_small() throws Exception {
+ MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
+ 5, 5, 4, 4,
+ 4, 4, 3, 4
+ );
+ verifier.migrate(new Point(5, 5), new Point(3, 4));
+ verifier.assertCompleted();
+ }
+
+ @Test
+ public void testMultiStepMigration_zig_zag() throws Exception {
+ MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
+ 5, 7, 4, 7,
+ 4, 7, 3, 7
+ );
+ verifier.migrate(new Point(5, 5), new Point(3, 7));
+ verifier.assertCompleted();
+ }
+
+ private static class MultiStepMigrationTaskVerifier extends MultiStepMigrationTask {
+
+ private final LinkedList<Point> mPoints;
+
+ public MultiStepMigrationTaskVerifier(int... points) {
+ super(null, null);
+
+ mPoints = new LinkedList<>();
+ for (int i = 0; i < points.length; i += 2) {
+ mPoints.add(new Point(points[i], points[i + 1]));
+ }
+ }
+
+ @Override
+ protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
+ assertEquals(sourceSize, mPoints.poll());
+ assertEquals(nextSize, mPoints.poll());
+ return false;
+ }
+
+ public void assertCompleted() {
+ assertTrue(mPoints.isEmpty());
+ }
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
new file mode 100644
index 000000000..c7b4613bd
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java
@@ -0,0 +1,70 @@
+package com.android.launcher3.model;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.compat.PackageInstallerCompat;
+import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * Tests for {@link PackageInstallStateChangedTask}
+ */
+@RunWith(RobolectricTestRunner.class)
+public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase {
+
+ @Before
+ public void initData() throws Exception {
+ initializeData("/package_install_state_change_task_data.txt");
+ }
+
+ private PackageInstallStateChangedTask newTask(String pkg, int progress) {
+ int state = PackageInstallerCompat.STATUS_INSTALLING;
+ PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress);
+ return new PackageInstallStateChangedTask(installInfo);
+ }
+
+ @Test
+ public void testSessionUpdate_ignore_installed() throws Exception {
+ executeTaskForTest(newTask("app1", 30));
+
+ // No shortcuts were updated
+ verifyProgressUpdate(0);
+ }
+
+ @Test
+ public void testSessionUpdate_shortcuts_updated() throws Exception {
+ executeTaskForTest(newTask("app3", 30));
+
+ verifyProgressUpdate(30, 5, 6, 7);
+ }
+
+ @Test
+ public void testSessionUpdate_widgets_updated() throws Exception {
+ executeTaskForTest(newTask("app4", 30));
+
+ verifyProgressUpdate(30, 8, 9);
+ }
+
+ private void verifyProgressUpdate(int progress, Integer... idsUpdated) {
+ HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
+ for (ItemInfo info : bgDataModel.itemsIdMap) {
+ if (info instanceof ShortcutInfo) {
+ assertEquals(updates.contains(info.id) ? progress: 0,
+ ((ShortcutInfo) info).getInstallProgress());
+ } else {
+ assertEquals(updates.contains(info.id) ? progress: -1,
+ ((LauncherAppWidgetInfo) info).installProgress);
+ }
+ }
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
index faec380af..8513353dc 100644
--- a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
+++ b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java
@@ -21,7 +21,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -31,7 +30,6 @@ import static org.junit.Assert.assertTrue;
* Robolectric unit tests for {@link IntSet}
*/
@RunWith(RobolectricTestRunner.class)
-@Config(sdk = 26)
public class IntSetTest {
@Test
diff --git a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
new file mode 100644
index 000000000..32808f572
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java
@@ -0,0 +1,46 @@
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import com.android.launcher3.LauncherProvider;
+
+/**
+ * An extension of LauncherProvider backed up by in-memory database.
+ */
+public class TestLauncherProvider extends LauncherProvider {
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ protected synchronized void createDbIfNotExists() {
+ if (mOpenHelper == null) {
+ mOpenHelper = new MyDatabaseHelper(getContext());
+ }
+ }
+
+ @Override
+ protected void notifyListeners() { }
+
+ private static class MyDatabaseHelper extends DatabaseHelper {
+ public MyDatabaseHelper(Context context) {
+ super(context, null, null);
+ initIds();
+ }
+
+ @Override
+ public long getDefaultUserSerial() {
+ return 0;
+ }
+
+ @Override
+ protected void onEmptyDbCreated() { }
+
+ @Override
+ protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { }
+ }
+}