update:2021.03.19

fix:重新提交
add:
This commit is contained in:
FHT
2021-03-19 18:19:49 +08:00
parent d4399ffa61
commit d23777e680
1191 changed files with 149106 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
# 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.
#############################################
# Launcher Robolectric test target. #
#############################################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := LauncherRoboTests
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
androidx.test.runner \
androidx.test.rules \
mockito-robolectric-prebuilt \
truth-prebuilt
LOCAL_JAVA_LIBRARIES := \
platform-robolectric-3.6.1-prebuilt
LOCAL_JAVA_RESOURCE_DIRS := resources config
LOCAL_INSTRUMENTATION_FOR := Launcher3
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_JAVA_LIBRARY)
############################################
# Target to run the previous target. #
############################################
include $(CLEAR_VARS)
LOCAL_MODULE := RunLauncherRoboTests
LOCAL_SDK_VERSION := current
LOCAL_JAVA_LIBRARIES := \
LauncherRoboTests
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_TEST_PACKAGE := Launcher3
LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src \
LOCAL_ROBOTEST_TIMEOUT := 36000
include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk

View File

@@ -0,0 +1,2 @@
manifest=packages/apps/Launcher3/AndroidManifest.xml
sdk=26

View File

@@ -0,0 +1,36 @@
<!--
Copyright (C) 2019 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- overlayable_icons references all of the drawables in this package
that are being overlayed by resource overlays. If you remove/rename
any of these resources, you must also change the resource overlay icons.-->
<array name="overlayable_icons">
<item>@drawable/ic_corp</item>
<item>@drawable/ic_drag_handle</item>
<item>@drawable/ic_hourglass_top</item>
<item>@drawable/ic_info_no_shadow</item>
<item>@drawable/ic_install_no_shadow</item>
<item>@drawable/ic_palette</item>
<item>@drawable/ic_pin</item>
<item>@drawable/ic_remove_no_shadow</item>
<item>@drawable/ic_setting</item>
<item>@drawable/ic_smartspace_preferences</item>
<item>@drawable/ic_split_screen</item>
<item>@drawable/ic_uninstall_no_shadow</item>
<item>@drawable/ic_warning</item>
<item>@drawable/ic_widget</item>
</array>
</resources>

View File

@@ -0,0 +1,28 @@
# Model data used by CacheDataUpdatedTaskTest
classMap s com.android.uiuios.WorkspaceItemInfo
# Items for the BgDataModel
# App shortcuts
bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
# Auto install app shortcut
bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
# Custom shortcuts
bgItem s itemType=1 title=app1-shrt intent=component=app1/class3 id=7
bgItem s itemType=1 title=app4-shrt intent=component=app4/class1 id=8
# Restored custom shortcut
bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=9
bgItem s itemType=1 status=1 title=app5-shrt intent=component=app5/class1 id=10
allApps componentName=app1/class1 intent=component=app1/class1
allApps componentName=app1/class2 intent=component=app1/class2
allApps componentName=app2/class1 intent=component=app2/class1
allApps componentName=app2/class2 intent=component=app2/class2

View File

@@ -0,0 +1,4 @@
{
"version" : 10,
"downgrade_to_9" : []
}

View File

@@ -0,0 +1,24 @@
# Model data used by PackageInstallStateChangeTaskTest
classMap s com.android.uiuios.WorkspaceItemInfo
classMap w com.android.uiuios.LauncherAppWidgetInfo
# Items for the BgDataModel
# App shortcuts
bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
# Promise icons for app3
bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
# Promise icon for app4
bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
# Widget
bgItem w providerName=app4/provider1 id=9
bgItem w providerName=app5/provider1 id=10

View File

@@ -0,0 +1,116 @@
package com.android.uiuios.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.uiuios.config;
import com.android.uiuios.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.uiuios.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.uiuios.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.uiuios.ItemInfo;
import com.android.uiuios.LauncherSettings;
import com.android.uiuios.LauncherSettings.Favorites;
import com.android.uiuios.WorkspaceItemInfo;
import com.android.uiuios.util.ContentWriter;
import com.android.uiuios.util.GridOccupancy;
import com.android.uiuios.util.IntArray;
import com.android.uiuios.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.uiuios.model;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import com.android.uiuios.LauncherProvider;
import com.android.uiuios.LauncherSettings;
import com.android.uiuios.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.uiuios.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.uiuios.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.uiuios.AllAppsList;
import com.android.uiuios.AppFilter;
import com.android.uiuios.AppInfo;
import com.android.uiuios.InvariantDeviceProfile;
import com.android.uiuios.ItemInfo;
import com.android.uiuios.LauncherAppState;
import com.android.uiuios.LauncherModel;
import com.android.uiuios.LauncherModel.Callbacks;
import com.android.uiuios.LauncherModel.ModelUpdateTask;
import com.android.uiuios.LauncherProvider;
import com.android.uiuios.icons.IconCache;
import com.android.uiuios.icons.cache.CachingLogic;
import com.android.uiuios.util.ComponentKey;
import com.android.uiuios.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.uiuios.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.uiuios.AppInfo;
import com.android.uiuios.ItemInfo;
import com.android.uiuios.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.uiuios.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.uiuios.LauncherProvider;
import com.android.uiuios.LauncherProvider.DatabaseHelper;
import com.android.uiuios.LauncherSettings.Favorites;
import com.android.uiuios.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.uiuios.model;
import static android.database.DatabaseUtils.queryNumEntries;
import static com.android.uiuios.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
import static com.android.uiuios.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.uiuios.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.uiuios.LauncherSettings.Favorites;
import com.android.uiuios.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.uiuios.model;
import static com.android.uiuios.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.uiuios.InvariantDeviceProfile;
import com.android.uiuios.LauncherSettings;
import com.android.uiuios.config.FeatureFlags;
import com.android.uiuios.config.FlagOverrideRule;
import com.android.uiuios.model.GridSizeMigrationTask.MultiStepMigrationTask;
import com.android.uiuios.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.uiuios.model;
import static org.junit.Assert.assertEquals;
import com.android.uiuios.ItemInfo;
import com.android.uiuios.LauncherAppWidgetInfo;
import com.android.uiuios.WorkspaceItemInfo;
import com.android.uiuios.compat.PackageInstallerCompat;
import com.android.uiuios.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.uiuios.popup;
import static com.android.uiuios.popup.PopupPopulator.MAX_SHORTCUTS;
import static com.android.uiuios.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.uiuios.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.uiuios.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.uiuios.util;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.android.uiuios.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) { }
}
}