android10_Launcher3_original
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user