android10_Launcher3_original

This commit is contained in:
bryan.xiang
2020-02-29 10:01:57 +08:00
parent 7fd766734d
commit 6591b1156b
1146 changed files with 147198 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
package com.android.launcher3.config;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.robolectric.RuntimeEnvironment;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Test rule that makes overriding flags in Robolectric tests easier. This rule clears all flags
* before and after your test, avoiding one test method affecting subsequent methods.
*
* <p>Usage:
* <pre>
* {@literal @}Rule public final FlagOverrideRule flags = new FlagOverrideRule();
*
* {@literal @}FlagOverride(flag = "FOO", value=true)
* {@literal @}Test public void myTest() {
* ...
* }
* </pre>
*/
public final class FlagOverrideRule implements TestRule {
/**
* Container annotation for handling multiple {@link FlagOverride} annotations.
* <p>
* <p>Don't use this directly, use repeated {@link FlagOverride} annotations instead.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface FlagOverrides {
FlagOverride[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Repeatable(FlagOverrides.class)
public @interface FlagOverride {
String key();
boolean value();
}
private boolean ruleInProgress;
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
FeatureFlags.initialize(RuntimeEnvironment.application.getApplicationContext());
ruleInProgress = true;
try {
clearOverrides();
applyAnnotationOverrides(description);
base.evaluate();
} finally {
ruleInProgress = false;
clearOverrides();
}
}
};
}
private void override(BaseFlags.TogglableFlag flag, boolean newValue) {
if (!ruleInProgress) {
throw new IllegalStateException(
"Rule isn't in progress. Did you remember to mark it with @Rule?");
}
flag.setForTests(newValue);
}
private void applyAnnotationOverrides(Description description) {
for (Annotation annotation : description.getAnnotations()) {
if (annotation.annotationType() == FlagOverride.class) {
applyAnnotation((FlagOverride) annotation);
} else if (annotation.annotationType() == FlagOverrides.class) {
// Note: this branch is hit if the annotation is repeated
for (FlagOverride flagOverride : ((FlagOverrides) annotation).value()) {
applyAnnotation(flagOverride);
}
}
}
}
private void applyAnnotation(FlagOverride flagOverride) {
boolean found = false;
for (BaseFlags.TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
if (flag.getKey().equals(flagOverride.key())) {
override(flag, flagOverride.value());
found = true;
break;
}
}
if (!found) {
throw new IllegalStateException("Flag " + flagOverride.key() + " not found");
}
}
/**
* Resets all flags to their default values.
*/
private void clearOverrides() {
for (BaseFlags.TogglableFlag flag : FeatureFlags.getTogglableFlags()) {
flag.setForTests(flag.getDefaultValue());
}
}
}

View File

@@ -0,0 +1,36 @@
package com.android.launcher3.config;
import com.android.launcher3.config.FlagOverrideRule.FlagOverride;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Sample Robolectric test that demonstrates flag-overriding.
*/
@RunWith(RobolectricTestRunner.class)
public class FlagOverrideSampleTest {
// Check out https://junit.org/junit4/javadoc/4.12/org/junit/Rule.html for more information
// on @Rules.
@Rule
public final FlagOverrideRule flags = new FlagOverrideRule();
@FlagOverride(key = "EXAMPLE_FLAG", value = true)
@Test
public void withFlagOn() {
assertTrue(FeatureFlags.EXAMPLE_FLAG.get());
}
@FlagOverride(key = "EXAMPLE_FLAG", value = false)
@Test
public void withFlagOff() {
assertFalse(FeatureFlags.EXAMPLE_FLAG.get());
}
}

View File

@@ -0,0 +1,91 @@
package com.android.launcher3.logging;
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 java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Calendar;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link FileLog}
*/
@RunWith(RobolectricTestRunner.class)
public class FileLogTest {
private File mTempDir;
@Before
public void setUp() throws Exception {
int count = 0;
do {
mTempDir = new File(RuntimeEnvironment.application.getCacheDir(),
"log-test-" + (count++));
} while (!mTempDir.mkdir());
FileLog.setDir(mTempDir);
}
@After
public void tearDown() throws Exception {
// Clear existing logs
new File(mTempDir, "log-0").delete();
new File(mTempDir, "log-1").delete();
mTempDir.delete();
}
@Test
public void testPrintLog() throws Exception {
if (!FileLog.ENABLED) {
return;
}
FileLog.print("Testing", "hoolalala");
StringWriter writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("hoolalala"));
FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("abracadabra"));
// Exception is also printed
assertTrue(writer.toString().contains("cat! cat!"));
// Old logs still present after flush
assertTrue(writer.toString().contains("hoolalala"));
}
@Test
public void testOldFileTruncated() throws Exception {
if (!FileLog.ENABLED) {
return;
}
FileLog.print("Testing", "hoolalala");
StringWriter writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("hoolalala"));
Calendar threeDaysAgo = Calendar.getInstance();
threeDaysAgo.add(Calendar.HOUR, -72);
new File(mTempDir, "log-0").setLastModified(threeDaysAgo.getTimeInMillis());
new File(mTempDir, "log-1").setLastModified(threeDaysAgo.getTimeInMillis());
FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
writer = new StringWriter();
FileLog.flushAll(new PrintWriter(writer));
assertTrue(writer.toString().contains("abracadabra"));
// Exception is also printed
assertTrue(writer.toString().contains("cat! cat!"));
// Old logs have been truncated
assertFalse(writer.toString().contains("hoolalala"));
}
}

View File

@@ -0,0 +1,176 @@
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.Intent;
import android.graphics.Rect;
import android.util.Pair;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.WorkspaceItemInfo;
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 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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 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);
}
@Test
public void testFindSpaceForItem_prefers_second() throws Exception {
// First screen has only one hole of size 1
int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
// Second screen has 2 holes of sizes 3x2 and 2x3
setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
int[] spaceFound = newTask()
.findSpaceForItem(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));
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 {
WorkspaceItemInfo info = new WorkspaceItemInfo();
info.intent = new Intent().setComponent(mComponent1);
// Setup a screen with a hole
setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
// Nothing was added
assertTrue(executeTaskForTest(newTask(info)).isEmpty());
}
@Test
public void testAddItem_some_items_added() throws Exception {
WorkspaceItemInfo info = new WorkspaceItemInfo();
info.intent = new Intent().setComponent(mComponent1);
WorkspaceItemInfo info2 = new WorkspaceItemInfo();
info2.intent = new Intent().setComponent(mComponent2);
// Setup a screen with a hole
setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
executeTaskForTest(newTask(info, info2)).get(0).run();
ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
// only info2 should be added because info was already added to the workspace
// in setupWorkspaceWithHoles()
verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(),
animated.capture());
assertTrue(notAnimated.getValue().isEmpty());
assertEquals(1, animated.getValue().size());
assertTrue(animated.getValue().contains(info2));
}
private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception {
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);
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int x = 0; x < idp.numColumns; x++) {
for (int y = 0; y < idp.numRows; y++) {
if (!occupancy.cells[x][y]) {
continue;
}
WorkspaceItemInfo info = new WorkspaceItemInfo();
info.intent = new Intent().setComponent(mComponent1);
info.id = startId++;
info.screenId = screenId;
info.cellX = x;
info.cellY = y;
info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
bgDataModel.addItem(targetContext, 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));
});
}
}
executor.submit(() -> null).get();
executor.shutdown();
return startId;
}
}

View File

@@ -0,0 +1,121 @@
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;
}
}

View File

@@ -0,0 +1,221 @@
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.IconCache;
import com.android.launcher3.icons.cache.CachingLogic;
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;
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 Supplier<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);
}
}
}

View File

@@ -0,0 +1,99 @@
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.WorkspaceItemInfo;
import org.junit.Before;
import org.junit.Ignore;
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
@Ignore("This test fails with resource errors") // b/131115553
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
@Ignore("This test fails with resource errors") // b/131115553
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
@Ignore("This test fails with resource errors") // b/131115553
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(((WorkspaceItemInfo) info).iconBitmap);
} else {
assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
assertNull(((WorkspaceItemInfo) info).iconBitmap);
}
}
}
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright (C) 2017 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 junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
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;
/**
* Tests for {@link DbDowngradeHelper}
*/
@RunWith(RobolectricTestRunner.class)
public class DbDowngradeHelperTest {
private static final String SCHEMA_FILE = "test_schema.json";
private static final String DB_FILE = "test.db";
private Context mContext;
private File mSchemaFile;
private File mDbFile;
@Before
public void setup() {
mContext = RuntimeEnvironment.application;
mSchemaFile = mContext.getFileStreamPath(SCHEMA_FILE);
mDbFile = mContext.getDatabasePath(DB_FILE);
}
@Test
public void testDowngradeSchemaMatchesVersion() throws Exception {
mSchemaFile.delete();
assertFalse(mSchemaFile.exists());
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 0, mContext);
assertEquals(LauncherProvider.SCHEMA_VERSION, DbDowngradeHelper.parse(mSchemaFile).version);
}
@Test
public void testUpdateSchemaFile() throws Exception {
// Setup mock resources
Resources res = spy(mContext.getResources());
doAnswer(i ->this.getClass().getResourceAsStream("/db_schema_v10.json"))
.when(res).openRawResource(eq(R.raw.downgrade_schema));
Context context = spy(mContext);
when(context.getResources()).thenReturn(res);
mSchemaFile.delete();
assertFalse(mSchemaFile.exists());
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, context);
assertTrue(mSchemaFile.exists());
assertEquals(10, DbDowngradeHelper.parse(mSchemaFile).version);
// Schema is updated on version upgrade
assertTrue(mSchemaFile.setLastModified(0));
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 11, context);
assertNotSame(0, mSchemaFile.lastModified());
// Schema is not updated when version is same
assertTrue(mSchemaFile.setLastModified(0));
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, context);
assertEquals(0, mSchemaFile.lastModified());
// Schema is not updated on version downgrade
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 3, context);
assertEquals(0, mSchemaFile.lastModified());
}
@Test
public void testDowngrade_success_v24() throws Exception {
setupTestDb();
TestOpenHelper helper = new TestOpenHelper(24);
assertEquals(24, helper.getReadableDatabase().getVersion());
helper.close();
}
@Test
public void testDowngrade_success_v22() throws Exception {
setupTestDb();
SQLiteOpenHelper helper = new TestOpenHelper(22);
assertEquals(22, helper.getWritableDatabase().getVersion());
// Check column does not exist
try (Cursor c = helper.getWritableDatabase().query(Favorites.TABLE_NAME,
null, null, null, null, null, null)) {
assertEquals(-1, c.getColumnIndex(Favorites.OPTIONS));
// Check data is present
assertEquals(10, c.getCount());
}
helper.close();
helper = new DatabaseHelper(mContext, null, DB_FILE) {
@Override
public void onOpen(SQLiteDatabase db) { }
};
assertEquals(LauncherProvider.SCHEMA_VERSION, helper.getWritableDatabase().getVersion());
try (Cursor c = helper.getWritableDatabase().query(Favorites.TABLE_NAME,
null, null, null, null, null, null)) {
// Check column exists
assertNotSame(-1, c.getColumnIndex(Favorites.OPTIONS));
// Check data is present
assertEquals(10, c.getCount());
}
helper.close();
}
@Test(expected = DowngradeFailException.class)
public void testDowngrade_fail_v20() throws Exception {
setupTestDb();
TestOpenHelper helper = new TestOpenHelper(20);
helper.getReadableDatabase().getVersion();
}
private void setupTestDb() throws Exception {
mSchemaFile.delete();
mDbFile.delete();
DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext);
DatabaseHelper dbHelper = new DatabaseHelper(mContext, null, DB_FILE) {
@Override
public void onOpen(SQLiteDatabase db) { }
};
// Insert dummy data
for (int i = 0; i < 10; i++) {
ContentValues values = new ContentValues();
values.put(Favorites._ID, i);
values.put(Favorites.TITLE, "title " + i);
dbHelper.getWritableDatabase().insert(Favorites.TABLE_NAME, null, values);
}
dbHelper.close();
}
private class TestOpenHelper extends SQLiteOpenHelper {
public TestOpenHelper(int version) {
super(mContext, DB_FILE, null, version);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
throw new RuntimeException("DB should already be created");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
throw new RuntimeException("Only downgrade supported");
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
try {
DbDowngradeHelper.parse(mSchemaFile).onDowngrade(db, oldVersion, newVersion);
} catch (Exception e) {
throw new DowngradeFailException(e);
}
}
}
private static class DowngradeFailException extends RuntimeException {
public DowngradeFailException(Exception e) {
super(e);
}
}
}

View File

@@ -0,0 +1,115 @@
package com.android.launcher3.model;
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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.ContentValues;
import android.graphics.Point;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Unit tests for {@link GridBackupTable}
*/
@RunWith(RobolectricTestRunner.class)
public class GridBackupTableTest extends BaseGridChangesTestCase {
private static final int BACKUP_ITEM_COUNT = 12;
@Before
public void setupGridData() {
createGrid(new int[][][]{{
{ APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
{ SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
{ NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
{ APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
}});
assertEquals(BACKUP_ITEM_COUNT, queryNumEntries(mDb, TABLE_NAME));
}
@Test
public void backupTableCreated() {
GridBackupTable backupTable = new GridBackupTable(mContext, mDb, 4, 4, 4);
assertFalse(backupTable.backupOrRestoreAsNeeded());
Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
// One extra entry for properties
assertEquals(BACKUP_ITEM_COUNT + 1, queryNumEntries(mDb, BACKUP_TABLE_NAME));
}
@Test
public void backupTableRestored() {
assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
// Delete entries
mDb.delete(TABLE_NAME, null, null);
assertEquals(0, queryNumEntries(mDb, TABLE_NAME));
GridBackupTable backupTable = new GridBackupTable(mContext, mDb, 3, 3, 3);
assertTrue(backupTable.backupOrRestoreAsNeeded());
// Items have been restored
assertEquals(BACKUP_ITEM_COUNT, queryNumEntries(mDb, TABLE_NAME));
Point outSize = new Point();
assertEquals(4, backupTable.getRestoreHotseatAndGridSize(outSize));
assertEquals(4, outSize.x);
assertEquals(4, outSize.y);
}
@Test
public void backupTableRemovedOnAdd() {
assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
addItem(1, 2, DESKTOP, 1, 1);
assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
}
@Test
public void backupTableRemovedOnDelete() {
assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
mContext.getContentResolver().delete(Favorites.CONTENT_URI, null, null);
assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
}
@Test
public void backupTableRetainedOnUpdate() {
assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
ContentValues values = new ContentValues();
values.put(Favorites.RANK, 4);
// Something was updated
assertTrue(mContext.getContentResolver()
.update(Favorites.CONTENT_URI, values, null, null) > 0);
// Backup table remains
assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
}
}

View File

@@ -0,0 +1,362 @@
package com.android.launcher3.model;
import static com.android.launcher3.model.GridSizeMigrationTask.getWorkspaceScreenIds;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.database.Cursor;
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 org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.HashSet;
import java.util.LinkedList;
/**
* Unit tests for {@link GridSizeMigrationTask}
*/
@RunWith(RobolectricTestRunner.class)
public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase {
@Rule
public final FlagOverrideRule flags = new FlagOverrideRule();
private HashSet<String> mValidPackages;
private InvariantDeviceProfile mIdp;
@Before
public void setUp() {
mValidPackages = new HashSet<>();
mValidPackages.add(TEST_PACKAGE);
mIdp = new InvariantDeviceProfile();
}
@Test
public void testHotseatMigration_apps_dropped() throws Exception {
int[] hotseatItems = {
addItem(APP_ICON, 0, HOTSEAT, 0, 0),
addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
-1,
addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
addItem(APP_ICON, 4, HOTSEAT, 0, 0),
};
mIdp.numHotseatIcons = 3;
new GridSizeMigrationTask(mContext, mDb, 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(APP_ICON, 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, mDb, 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, mDb, 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, mDb, 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, mDb, 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, mDb, 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},
}});
}
@Test
public void testWorkspace_first_row_blocked() throws Exception {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
return;
}
// 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, mDb, 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]},
}});
}
@Test
public void testWorkspace_items_moved_to_empty_first_row() throws Exception {
if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
return;
}
// 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, mDb, 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]},
}});
}
/**
* 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 = getWorkspaceScreenIds(mDb);
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();
}
@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, 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());
}
}
}

View File

@@ -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.WorkspaceItemInfo;
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 WorkspaceItemInfo) {
assertEquals(updates.contains(info.id) ? progress: 0,
((WorkspaceItemInfo) info).getInstallProgress());
} else {
assertEquals(updates.contains(info.id) ? progress: -1,
((LauncherAppWidgetInfo) info).installProgress);
}
}
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright (C) 2016 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.popup;
import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
import static com.android.launcher3.popup.PopupPopulator.NUM_DYNAMIC;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import android.content.pm.ShortcutInfo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Tests the sorting and filtering of shortcuts in {@link PopupPopulator}.
*/
@RunWith(RobolectricTestRunner.class)
public class PopupPopulatorTest {
@Test
public void testSortAndFilterShortcuts() {
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 3), 0, 3);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 0), MAX_SHORTCUTS, 0);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 5), 0, MAX_SHORTCUTS);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 3),
MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 5),
MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 1), MAX_SHORTCUTS - 1, 1);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(1, 5), 1, MAX_SHORTCUTS - 1);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(5, 3),
MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 5),
MAX_SHORTCUTS - NUM_DYNAMIC, NUM_DYNAMIC);
}
@Test
public void testDeDupeShortcutId() {
// Successfully remove one of the shortcuts
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 2, 0, generateId(true, 1));
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(0, 3), 0, 2, generateId(false, 1));
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 1, generateId(false, 1));
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 1, 2, generateId(true, 1));
// Successfully keep all shortcuts when id doesn't exist
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0, generateId(false, 1));
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(3, 0), 3, 0, generateId(true, 4));
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 2, generateId(false, 4));
filterShortcutsAndAssertNumStaticAndDynamic(createShortcutsList(2, 2), 2, 2, generateId(true, 4));
}
private String generateId(boolean isStatic, int rank) {
return (isStatic ? "static" : "dynamic") + rank;
}
private void filterShortcutsAndAssertNumStaticAndDynamic(
List<ShortcutInfo> shortcuts, int expectedStatic, int expectedDynamic) {
filterShortcutsAndAssertNumStaticAndDynamic(shortcuts, expectedStatic, expectedDynamic, null);
}
private void filterShortcutsAndAssertNumStaticAndDynamic(List<ShortcutInfo> shortcuts,
int expectedStatic, int expectedDynamic, String shortcutIdToRemove) {
Collections.shuffle(shortcuts);
List<ShortcutInfo> filteredShortcuts = PopupPopulator.sortAndFilterShortcuts(
shortcuts, shortcutIdToRemove);
assertIsSorted(filteredShortcuts);
int numStatic = 0;
int numDynamic = 0;
for (ShortcutInfo shortcut : filteredShortcuts) {
if (shortcut.isDeclaredInManifest()) {
numStatic++;
}
if (shortcut.isDynamic()) {
numDynamic++;
}
}
assertEquals(expectedStatic, numStatic);
assertEquals(expectedDynamic, numDynamic);
}
private void assertIsSorted(List<ShortcutInfo> shortcuts) {
int lastStaticRank = -1;
int lastDynamicRank = -1;
boolean hasSeenDynamic = false;
for (ShortcutInfo shortcut : shortcuts) {
int rank = shortcut.getRank();
if (shortcut.isDeclaredInManifest()) {
assertFalse("Static shortcuts should come before all dynamic shortcuts.",
hasSeenDynamic);
assertTrue(rank > lastStaticRank);
lastStaticRank = rank;
}
if (shortcut.isDynamic()) {
hasSeenDynamic = true;
assertTrue(rank > lastDynamicRank);
lastDynamicRank = rank;
}
}
}
private List<ShortcutInfo> createShortcutsList(int numStatic, int numDynamic) {
List<ShortcutInfo> shortcuts = new ArrayList<>();
for (int i = 0; i < numStatic; i++) {
shortcuts.add(createInfo(true, i));
}
for (int i = 0; i < numDynamic; i++) {
shortcuts.add(createInfo(false, i));
}
return shortcuts;
}
private ShortcutInfo createInfo(boolean isStatic, int rank) {
ShortcutInfo info = spy(new ShortcutInfo.Builder(
RuntimeEnvironment.application, generateId(isStatic, rank))
.setRank(rank)
.build());
doReturn(isStatic).when(info).isDeclaredInManifest();
doReturn(!isStatic).when(info).isDynamic();
return info;
}
}

View File

@@ -0,0 +1,67 @@
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;
import static org.junit.Assert.assertTrue;
/**
* Unit tests for {@link GridOccupancy}
*/
@RunWith(RobolectricTestRunner.class)
public class GridOccupancyTest {
@Test
public void testFindVacantCell() {
GridOccupancy grid = initGrid(4,
1, 1, 1, 0, 0,
0, 0, 1, 1, 0,
0, 0, 0, 0, 0,
1, 1, 0, 0, 0
);
int[] vacant = new int[2];
assertTrue(grid.findVacantCell(vacant, 2, 2));
assertEquals(vacant[0], 0);
assertEquals(vacant[1], 1);
assertTrue(grid.findVacantCell(vacant, 3, 2));
assertEquals(vacant[0], 2);
assertEquals(vacant[1], 2);
assertFalse(grid.findVacantCell(vacant, 3, 3));
}
@Test
public void testIsRegionVacant() {
GridOccupancy grid = initGrid(4,
1, 1, 1, 0, 0,
0, 0, 1, 1, 0,
0, 0, 0, 0, 0,
1, 1, 0, 0, 0
);
assertTrue(grid.isRegionVacant(4, 0, 1, 4));
assertTrue(grid.isRegionVacant(0, 1, 2, 2));
assertTrue(grid.isRegionVacant(2, 2, 3, 2));
assertFalse(grid.isRegionVacant(3, 0, 2, 4));
assertFalse(grid.isRegionVacant(0, 0, 2, 1));
}
private GridOccupancy initGrid(int rows, int... cells) {
int cols = cells.length / rows;
int i = 0;
GridOccupancy grid = new GridOccupancy(cols, rows);
for (int y = 0; y < rows; y++) {
for (int x = 0; x < cols; x++) {
grid.cells[x][y] = cells[i] != 0;
i++;
}
}
return grid;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2018 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.google.common.truth.Truth.assertThat;
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;
import static org.junit.Assert.assertTrue;
/**
* Robolectric unit tests for {@link IntSet}
*/
@RunWith(RobolectricTestRunner.class)
public class IntSetTest {
@Test
public void shouldBeEmptyInitially() {
IntSet set = new IntSet();
assertThat(set.size()).isEqualTo(0);
}
@Test
public void oneElementSet() {
IntSet set = new IntSet();
set.add(2);
assertThat(set.size()).isEqualTo(1);
assertTrue(set.contains(2));
assertFalse(set.contains(1));
}
@Test
public void twoElementSet() {
IntSet set = new IntSet();
set.add(2);
set.add(1);
assertThat(set.size()).isEqualTo(2);
assertTrue(set.contains(2));
assertTrue(set.contains(1));
}
@Test
public void threeElementSet() {
IntSet set = new IntSet();
set.add(2);
set.add(1);
set.add(10);
assertThat(set.size()).isEqualTo(3);
assertEquals("1, 2, 10", set.mArray.toConcatString());
}
@Test
public void duplicateEntries() {
IntSet set = new IntSet();
set.add(2);
set.add(2);
assertEquals(1, set.size());
}
}

View File

@@ -0,0 +1,51 @@
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());
}
}
public SQLiteDatabase getDb() {
createDbIfNotExists();
return mOpenHelper.getWritableDatabase();
}
@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) { }
}
}