Merge "Removing support for lagacy shortcuts" into udc-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
e607fdb2a2
@@ -17,17 +17,18 @@ package com.android.launcher3.model
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import com.android.launcher3.InvariantDeviceProfile
|
||||
import com.android.launcher3.LauncherAppState
|
||||
import com.android.launcher3.LauncherSettings
|
||||
import com.android.launcher3.model.data.AppInfo
|
||||
import com.android.launcher3.model.data.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 com.android.launcher3.util.LauncherLayoutBuilder
|
||||
import com.android.launcher3.util.LauncherModelHelper
|
||||
import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY
|
||||
import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE
|
||||
import java.util.UUID
|
||||
|
||||
/** Base class for workspace related tests. */
|
||||
@@ -38,6 +39,7 @@ abstract class AbstractWorkspaceModelTest {
|
||||
val nonEmptyScreenSpaces = listOf(Rect(1, 2, 3, 4))
|
||||
}
|
||||
|
||||
protected lateinit var mLayoutBuilder: LauncherLayoutBuilder
|
||||
protected lateinit var mTargetContext: Context
|
||||
protected lateinit var mIdp: InvariantDeviceProfile
|
||||
protected lateinit var mAppState: LauncherAppState
|
||||
@@ -47,6 +49,7 @@ abstract class AbstractWorkspaceModelTest {
|
||||
protected lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy>
|
||||
|
||||
open fun setup() {
|
||||
mLayoutBuilder = LauncherLayoutBuilder()
|
||||
mModelHelper = LauncherModelHelper()
|
||||
mTargetContext = mModelHelper.sandboxContext
|
||||
mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext]
|
||||
@@ -64,10 +67,11 @@ abstract class AbstractWorkspaceModelTest {
|
||||
|
||||
/** Sets up workspaces with the given screen IDs with some items and a 2x2 space. */
|
||||
fun setupWorkspaces(screenIdsWithItems: List<Int>) {
|
||||
var nextItemId = 1
|
||||
screenIdsWithItems.forEach { screenId ->
|
||||
nextItemId = setupWorkspace(nextItemId, screenId, nonEmptyScreenSpaces)
|
||||
}
|
||||
screenIdsWithItems.forEach { screenId -> setupWorkspace(screenId, nonEmptyScreenSpaces) }
|
||||
mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder)
|
||||
mIdp.numRows = 5
|
||||
mIdp.numColumns = mIdp.numRows
|
||||
mModelHelper.loadModelSync()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,30 +82,23 @@ abstract class AbstractWorkspaceModelTest {
|
||||
screen1: List<Rect>? = null,
|
||||
screen2: List<Rect>? = null,
|
||||
screen3: List<Rect>? = null,
|
||||
) = listOf(screen0, screen1, screen2, screen3).let(this::setupWithSpaces)
|
||||
) {
|
||||
listOf(screen0, screen1, screen2, screen3).let(this::setupWithSpaces)
|
||||
mModelHelper.setupDefaultLayoutProvider(mLayoutBuilder)
|
||||
mIdp.numRows = 5
|
||||
mIdp.numColumns = mIdp.numRows
|
||||
mModelHelper.loadModelSync()
|
||||
}
|
||||
|
||||
private fun setupWithSpaces(workspaceSpaces: List<List<Rect>?>) {
|
||||
var nextItemId = 1
|
||||
workspaceSpaces.forEachIndexed { screenId, spaces ->
|
||||
if (spaces != null) {
|
||||
nextItemId = setupWorkspace(nextItemId, screenId, spaces)
|
||||
setupWorkspace(screenId, spaces)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWorkspace(startId: Int, screenId: Int, spaces: List<Rect>): Int {
|
||||
return mModelHelper.executeSimpleTask { dataModel ->
|
||||
writeWorkspaceWithSpaces(dataModel, startId, screenId, spaces)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeWorkspaceWithSpaces(
|
||||
bgDataModel: BgDataModel,
|
||||
itemStartId: Int,
|
||||
screenId: Int,
|
||||
spaces: List<Rect>,
|
||||
): Int {
|
||||
var itemId = itemStartId
|
||||
private fun setupWorkspace(screenId: Int, spaces: List<Rect>) {
|
||||
val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows)
|
||||
occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true)
|
||||
spaces.forEach { spaceRect -> occupancy.markCells(spaceRect, false) }
|
||||
@@ -109,35 +106,22 @@ abstract class AbstractWorkspaceModelTest {
|
||||
mScreenOccupancy.append(screenId, occupancy)
|
||||
for (x in 0 until mIdp.numColumns) {
|
||||
for (y in 0 until mIdp.numRows) {
|
||||
if (!occupancy.cells[x][y]) {
|
||||
continue
|
||||
if (occupancy.cells[x][y]) {
|
||||
mLayoutBuilder.atWorkspace(x, y, screenId).putApp(TEST_PACKAGE, TEST_ACTIVITY)
|
||||
}
|
||||
val info = getExistingItem()
|
||||
info.id = itemId++
|
||||
info.screenId = screenId
|
||||
info.cellX = x
|
||||
info.cellY = y
|
||||
info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP
|
||||
bgDataModel.addItem(mTargetContext, info, false)
|
||||
val writer = ContentWriter(mTargetContext)
|
||||
info.writeToValues(writer)
|
||||
writer.put(LauncherSettings.Favorites._ID, info.id)
|
||||
mTargetContext.contentResolver.insert(
|
||||
LauncherSettings.Favorites.CONTENT_URI,
|
||||
writer.getValues(mTargetContext)
|
||||
)
|
||||
}
|
||||
}
|
||||
return itemId
|
||||
}
|
||||
|
||||
fun getExistingItem() =
|
||||
WorkspaceItemInfo().apply { intent = Intent().setComponent(ComponentName("a", "b")) }
|
||||
WorkspaceItemInfo().apply {
|
||||
intent = AppInfo.makeLaunchIntent(ComponentName(TEST_PACKAGE, TEST_ACTIVITY))
|
||||
}
|
||||
|
||||
fun getNewItem(): WorkspaceItemInfo {
|
||||
val itemPackage = UUID.randomUUID().toString()
|
||||
return WorkspaceItemInfo().apply {
|
||||
intent = Intent().setComponent(ComponentName(itemPackage, itemPackage))
|
||||
intent = AppInfo.makeLaunchIntent(ComponentName(itemPackage, itemPackage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.android.launcher3.model.data.ItemInfo
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo
|
||||
import com.android.launcher3.util.Executors
|
||||
import com.android.launcher3.util.IntArray
|
||||
import com.android.launcher3.util.IntSet
|
||||
import com.android.launcher3.util.TestUtil.runOnExecutorSync
|
||||
import com.android.launcher3.util.any
|
||||
import com.android.launcher3.util.eq
|
||||
import com.android.launcher3.util.same
|
||||
@@ -32,8 +32,6 @@ import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
@@ -46,11 +44,7 @@ import org.mockito.MockitoAnnotations
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
|
||||
|
||||
@Captor private lateinit var mAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
|
||||
|
||||
@Captor private lateinit var mNotAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
|
||||
|
||||
@Mock private lateinit var mDataModelCallbacks: BgDataModel.Callbacks
|
||||
private lateinit var mDataModelCallbacks: MyCallbacks
|
||||
|
||||
@Mock private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder
|
||||
|
||||
@@ -58,7 +52,7 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
|
||||
override fun setup() {
|
||||
super.setup()
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(mDataModelCallbacks.getPagesToBindSynchronously(any())).thenReturn(IntSet())
|
||||
mDataModelCallbacks = MyCallbacks()
|
||||
Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) }
|
||||
.get()
|
||||
}
|
||||
@@ -105,7 +99,7 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
|
||||
val addedItems = testAddItems(nonEmptyScreenIds, itemToAdd)
|
||||
|
||||
assertThat(addedItems.size).isEqualTo(0)
|
||||
verifyZeroInteractions(mWorkspaceItemSpaceFinder, mDataModelCallbacks)
|
||||
verifyZeroInteractions(mWorkspaceItemSpaceFinder)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -191,22 +185,14 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
|
||||
): List<AddedItem> {
|
||||
setupWorkspaces(nonEmptyScreenIds)
|
||||
val task = newTask(*itemsToAdd)
|
||||
var updateCount = 0
|
||||
mModelHelper.executeTaskForTest(task).forEach {
|
||||
updateCount++
|
||||
it.run()
|
||||
}
|
||||
|
||||
val addedItems = mutableListOf<AddedItem>()
|
||||
if (updateCount > 0) {
|
||||
verify(mDataModelCallbacks)
|
||||
.bindAppsAdded(
|
||||
any(),
|
||||
mNotAnimatedItemArgumentCaptor.capture(),
|
||||
mAnimatedItemArgumentCaptor.capture()
|
||||
)
|
||||
addedItems.addAll(mAnimatedItemArgumentCaptor.value.map { AddedItem(it, true) })
|
||||
addedItems.addAll(mNotAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
|
||||
|
||||
runOnExecutorSync(Executors.MODEL_EXECUTOR) {
|
||||
mDataModelCallbacks.addedItems.clear()
|
||||
mModelHelper.model.enqueueModelUpdateTask(task)
|
||||
runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
|
||||
addedItems.addAll(mDataModelCallbacks.addedItems)
|
||||
}
|
||||
|
||||
return addedItems
|
||||
@@ -224,3 +210,17 @@ class AddWorkspaceItemsTaskTest : AbstractWorkspaceModelTest() {
|
||||
}
|
||||
|
||||
private data class AddedItem(val itemInfo: ItemInfo, val isAnimated: Boolean)
|
||||
|
||||
private class MyCallbacks : BgDataModel.Callbacks {
|
||||
|
||||
val addedItems = mutableListOf<AddedItem>()
|
||||
|
||||
override fun bindAppsAdded(
|
||||
newScreens: IntArray?,
|
||||
addNotAnimated: ArrayList<ItemInfo>,
|
||||
addAnimated: ArrayList<ItemInfo>
|
||||
) {
|
||||
addedItems.addAll(addAnimated.map { AddedItem(it, true) })
|
||||
addedItems.addAll(addNotAnimated.map { AddedItem(it, false) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static android.os.Process.myUserHandle;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
|
||||
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Color;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
import com.android.launcher3.icons.cache.CachingLogic;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.LauncherLayoutBuilder;
|
||||
import com.android.launcher3.util.LauncherModelHelper;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -35,6 +34,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests for {@link CacheDataUpdatedTask}
|
||||
@@ -43,49 +43,40 @@ import java.util.HashSet;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class CacheDataUpdatedTaskTest {
|
||||
|
||||
private static final String NEW_LABEL_PREFIX = "new-label-";
|
||||
private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
|
||||
private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
|
||||
|
||||
private LauncherModelHelper mModelHelper;
|
||||
private Context mContext;
|
||||
|
||||
private int mSession1;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
mModelHelper = new LauncherModelHelper();
|
||||
mModelHelper.initializeData("cache_data_updated_task_data");
|
||||
mContext = mModelHelper.sandboxContext;
|
||||
mSession1 = mModelHelper.createInstallerSession(PENDING_APP_1);
|
||||
mModelHelper.createInstallerSession(PENDING_APP_2);
|
||||
|
||||
// Add placeholder entries in the cache to simulate update
|
||||
Context context = mModelHelper.sandboxContext;
|
||||
IconCache iconCache = LauncherAppState.getInstance(context).getIconCache();
|
||||
CachingLogic<ItemInfo> placeholderLogic = new CachingLogic<ItemInfo>() {
|
||||
@Override
|
||||
@NonNull
|
||||
public ComponentName getComponent(@NonNull ItemInfo info) {
|
||||
return info.getTargetComponent();
|
||||
}
|
||||
LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
|
||||
.atHotseat(1).putFolder("MyFolder")
|
||||
.addApp(TEST_PACKAGE, TEST_ACTIVITY) // 2
|
||||
.addApp(TEST_PACKAGE, TEST_ACTIVITY2) // 3
|
||||
.addApp(TEST_PACKAGE, TEST_ACTIVITY3) // 4
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public UserHandle getUser(@NonNull ItemInfo info) {
|
||||
return info.user;
|
||||
}
|
||||
// Pending App 1
|
||||
.addApp(PENDING_APP_1, TEST_ACTIVITY) // 5
|
||||
.addApp(PENDING_APP_1, TEST_ACTIVITY2) // 6
|
||||
.addApp(PENDING_APP_1, TEST_ACTIVITY3) // 7
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CharSequence getLabel(@NonNull ItemInfo info) {
|
||||
return NEW_LABEL_PREFIX + info.id;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BitmapInfo loadIcon(@NonNull Context context, @NonNull ItemInfo info) {
|
||||
return BitmapInfo.of(Bitmap.createBitmap(1, 1, Config.ARGB_8888), Color.RED);
|
||||
}
|
||||
};
|
||||
|
||||
UserManager um = context.getSystemService(UserManager.class);
|
||||
for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
|
||||
iconCache.addIconToDBAndMemCache(info, placeholderLogic, new PackageInfo(),
|
||||
um.getSerialNumberForUser(info.user), true);
|
||||
}
|
||||
// Pending App 2
|
||||
.addApp(PENDING_APP_2, TEST_ACTIVITY) // 8
|
||||
.addApp(PENDING_APP_2, TEST_ACTIVITY2) // 9
|
||||
.addApp(PENDING_APP_2, TEST_ACTIVITY3) // 10
|
||||
.build();
|
||||
mModelHelper.setupDefaultLayoutProvider(builder);
|
||||
mModelHelper.loadModelSync();
|
||||
assertEquals(10, mModelHelper.getBgDataModel().itemsIdMap.size());
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -94,27 +85,63 @@ public class CacheDataUpdatedTaskTest {
|
||||
}
|
||||
|
||||
private CacheDataUpdatedTask newTask(int op, String... pkg) {
|
||||
return new CacheDataUpdatedTask(op, Process.myUserHandle(),
|
||||
return new CacheDataUpdatedTask(op, myUserHandle(),
|
||||
new HashSet<>(Arrays.asList(pkg)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheUpdate_update_apps() throws Exception {
|
||||
// Clear all icons from apps list so that its easy to check what was updated
|
||||
for (AppInfo info : mModelHelper.getAllAppsList().data) {
|
||||
info.bitmap = BitmapInfo.LOW_RES_INFO;
|
||||
}
|
||||
public void testCacheUpdate_update_apps() {
|
||||
// Run on model executor so that no other task runs in the middle.
|
||||
runOnExecutorSync(MODEL_EXECUTOR, () -> {
|
||||
// Clear all icons from apps list so that its easy to check what was updated
|
||||
allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
|
||||
|
||||
mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
|
||||
mModelHelper.getModel().enqueueModelUpdateTask(
|
||||
newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, TEST_PACKAGE));
|
||||
|
||||
// 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 the app icons of TEST_PACKAGE (id 2, 3, 4) are updated.
|
||||
verifyUpdate(2, 3, 4);
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that only app1 var updated in allAppsList
|
||||
assertFalse(mModelHelper.getAllAppsList().data.isEmpty());
|
||||
for (AppInfo info : mModelHelper.getAllAppsList().data) {
|
||||
if (info.componentName.getPackageName().equals("app1")) {
|
||||
@Test
|
||||
public void testSessionUpdate_ignores_normal_apps() {
|
||||
// Run on model executor so that no other task runs in the middle.
|
||||
runOnExecutorSync(MODEL_EXECUTOR, () -> {
|
||||
// Clear all icons from apps list so that its easy to check what was updated
|
||||
allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
|
||||
|
||||
mModelHelper.getModel().enqueueModelUpdateTask(
|
||||
newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, TEST_PACKAGE));
|
||||
|
||||
// TEST_PACKAGE has no restored shortcuts. Verify that nothing was updated.
|
||||
verifyUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionUpdate_updates_pending_apps() {
|
||||
// Run on model executor so that no other task runs in the middle.
|
||||
runOnExecutorSync(MODEL_EXECUTOR, () -> {
|
||||
LauncherAppState.getInstance(mContext).getIconCache().updateSessionCache(
|
||||
new PackageUserKey(PENDING_APP_1, myUserHandle()),
|
||||
mContext.getPackageManager().getPackageInstaller().getSessionInfo(mSession1));
|
||||
|
||||
// Clear all icons from apps list so that its easy to check what was updated
|
||||
allItems().forEach(wi -> wi.bitmap = BitmapInfo.LOW_RES_INFO);
|
||||
|
||||
mModelHelper.getModel().enqueueModelUpdateTask(
|
||||
newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, PENDING_APP_1));
|
||||
|
||||
// Only restored apps from PENDING_APP_1 (id 5, 6, 7) are updated
|
||||
verifyUpdate(5, 6, 7);
|
||||
});
|
||||
}
|
||||
|
||||
private void verifyUpdate(int... idsUpdated) {
|
||||
IntSet updates = IntSet.wrap(idsUpdated);
|
||||
for (WorkspaceItemInfo info : allItems()) {
|
||||
if (updates.contains(info.id)) {
|
||||
assertFalse(info.bitmap.isNullOrLowRes());
|
||||
} else {
|
||||
assertTrue(info.bitmap.isNullOrLowRes());
|
||||
@@ -122,33 +149,7 @@ public class CacheDataUpdatedTaskTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionUpdate_ignores_normal_apps() throws Exception {
|
||||
mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
|
||||
|
||||
// app1 has no restored shortcuts. Verify that nothing was updated.
|
||||
verifyUpdate();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionUpdate_updates_pending_apps() throws Exception {
|
||||
mModelHelper.executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
|
||||
|
||||
// app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
|
||||
// were updated
|
||||
verifyUpdate(5, 6);
|
||||
}
|
||||
|
||||
private void verifyUpdate(Integer... idsUpdated) {
|
||||
HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
|
||||
for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
|
||||
if (updates.contains(info.id)) {
|
||||
assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
|
||||
assertFalse(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
|
||||
} else {
|
||||
assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
|
||||
assertTrue(((WorkspaceItemInfo) info).bitmap.isNullOrLowRes());
|
||||
}
|
||||
}
|
||||
private List<WorkspaceItemInfo> allItems() {
|
||||
return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).contents;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import android.graphics.Point
|
||||
import android.os.Process
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.launcher3.InvariantDeviceProfile
|
||||
import com.android.launcher3.LauncherPrefs
|
||||
import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE
|
||||
@@ -108,8 +107,8 @@ class GridSizeMigrationUtilTest {
|
||||
fun testMigration() {
|
||||
// Src Hotseat icons
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_DEEP_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
|
||||
// Src grid icons
|
||||
// _ _ _ _ _
|
||||
@@ -124,7 +123,7 @@ class GridSizeMigrationUtilTest {
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 4, 3, testPackage9, 9, TMP_TABLE)
|
||||
|
||||
// Dest hotseat icons
|
||||
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2)
|
||||
addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2)
|
||||
// Dest grid icons
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 2, testPackage10)
|
||||
|
||||
@@ -219,8 +218,8 @@ class GridSizeMigrationUtilTest {
|
||||
// Hotseat items in grid A
|
||||
// 1 2 _ 3 4
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_DEEP_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
|
||||
// Workspace items in grid A
|
||||
// _ _ _ _ _
|
||||
@@ -235,7 +234,7 @@ class GridSizeMigrationUtilTest {
|
||||
|
||||
// Hotseat items in grid B
|
||||
// 2 _ _ _
|
||||
addItem(ITEM_TYPE_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2)
|
||||
addItem(ITEM_TYPE_DEEP_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2)
|
||||
// Workspace items in grid B
|
||||
// _ _ _ _
|
||||
// _ _ _ 10
|
||||
@@ -291,7 +290,7 @@ class GridSizeMigrationUtilTest {
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
var locMap = parseLocMap(context, c)
|
||||
var locMap = parseLocMap(c)
|
||||
// Expected items in grid B
|
||||
// _ _ _ _
|
||||
// 5 6 7 8
|
||||
@@ -348,7 +347,7 @@ class GridSizeMigrationUtilTest {
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
locMap = parseLocMap(context, c)
|
||||
locMap = parseLocMap(c)
|
||||
// Expected workspace items in grid A
|
||||
// _ _ _ _ _
|
||||
// _ _ _ _ 5
|
||||
@@ -410,7 +409,7 @@ class GridSizeMigrationUtilTest {
|
||||
null
|
||||
)
|
||||
?: throw IllegalStateException()
|
||||
locMap = parseLocMap(context, c)
|
||||
locMap = parseLocMap(c)
|
||||
// Expected workspace items in grid B
|
||||
// _ _ _ _
|
||||
// 5 6 _ 8
|
||||
@@ -436,7 +435,7 @@ class GridSizeMigrationUtilTest {
|
||||
c.close()
|
||||
}
|
||||
|
||||
private fun parseLocMap(context: Context, c: Cursor): Map<String, Triple<Int, Int, Int>> {
|
||||
private fun parseLocMap(c: Cursor): Map<String, Triple<Int, Int, Int>> {
|
||||
// Check workspace items
|
||||
val intentIndex = c.getColumnIndex(INTENT)
|
||||
val screenIndex = c.getColumnIndex(SCREEN)
|
||||
@@ -465,7 +464,16 @@ class GridSizeMigrationUtilTest {
|
||||
1,
|
||||
TMP_TABLE
|
||||
),
|
||||
addItem(ITEM_TYPE_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE),
|
||||
addItem(
|
||||
ITEM_TYPE_DEEP_SHORTCUT,
|
||||
1,
|
||||
CONTAINER_HOTSEAT,
|
||||
0,
|
||||
0,
|
||||
testPackage2,
|
||||
2,
|
||||
TMP_TABLE
|
||||
),
|
||||
addItem(
|
||||
ITEM_TYPE_APPLICATION,
|
||||
2,
|
||||
@@ -476,7 +484,16 @@ class GridSizeMigrationUtilTest {
|
||||
3,
|
||||
TMP_TABLE
|
||||
),
|
||||
addItem(ITEM_TYPE_SHORTCUT, 3, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
|
||||
addItem(
|
||||
ITEM_TYPE_DEEP_SHORTCUT,
|
||||
3,
|
||||
CONTAINER_HOTSEAT,
|
||||
0,
|
||||
0,
|
||||
testPackage4,
|
||||
4,
|
||||
TMP_TABLE
|
||||
)
|
||||
)
|
||||
val numSrcDatabaseHotseatIcons = srcHotseatItems.size
|
||||
idp.numDatabaseHotseatIcons = 6
|
||||
@@ -532,9 +549,9 @@ class GridSizeMigrationUtilTest {
|
||||
@Test
|
||||
fun migrateFromLargerHotseat() {
|
||||
addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_DEEP_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_SHORTCUT, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_DEEP_SHORTCUT, 4, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
|
||||
addItem(ITEM_TYPE_APPLICATION, 5, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE)
|
||||
|
||||
idp.numDatabaseHotseatIcons = 4
|
||||
|
||||
@@ -30,7 +30,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.ICON;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.OPTIONS;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.RANK;
|
||||
@@ -158,13 +158,13 @@ public class LoaderCursorTest {
|
||||
|
||||
@Test
|
||||
public void loadSimpleShortcut() {
|
||||
initCursor(ITEM_TYPE_SHORTCUT, "my-shortcut");
|
||||
initCursor(ITEM_TYPE_DEEP_SHORTCUT, "my-shortcut");
|
||||
assertTrue(mLoaderCursor.moveToNext());
|
||||
|
||||
WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem();
|
||||
assertTrue(mApp.getIconCache().isDefaultIcon(info.bitmap, info.user));
|
||||
assertEquals("my-shortcut", info.title);
|
||||
assertEquals(ITEM_TYPE_SHORTCUT, info.itemType);
|
||||
assertEquals(ITEM_TYPE_DEEP_SHORTCUT, info.itemType);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3;
|
||||
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
|
||||
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -9,6 +16,8 @@ import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.pm.PackageInstallInfo;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.LauncherLayoutBuilder;
|
||||
import com.android.launcher3.util.LauncherModelHelper;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -16,9 +25,6 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Tests for {@link PackageInstallStateChangedTask}
|
||||
*/
|
||||
@@ -26,12 +32,36 @@ import java.util.HashSet;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PackageInstallStateChangedTaskTest {
|
||||
|
||||
private static final String PENDING_APP_1 = TEST_PACKAGE + ".pending1";
|
||||
private static final String PENDING_APP_2 = TEST_PACKAGE + ".pending2";
|
||||
|
||||
private LauncherModelHelper mModelHelper;
|
||||
private IntSet mDownloadingApps;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
mModelHelper = new LauncherModelHelper();
|
||||
mModelHelper.initializeData("package_install_state_change_task_data");
|
||||
mModelHelper.createInstallerSession(PENDING_APP_1);
|
||||
mModelHelper.createInstallerSession(PENDING_APP_2);
|
||||
|
||||
LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
|
||||
.atWorkspace(0, 0, 1).putApp(TEST_PACKAGE, TEST_ACTIVITY) // 1
|
||||
.atWorkspace(0, 0, 2).putApp(TEST_PACKAGE, TEST_ACTIVITY2) // 2
|
||||
.atWorkspace(0, 0, 3).putApp(TEST_PACKAGE, TEST_ACTIVITY3) // 3
|
||||
|
||||
.atWorkspace(0, 0, 4).putApp(PENDING_APP_1, TEST_ACTIVITY) // 4
|
||||
.atWorkspace(0, 0, 5).putApp(PENDING_APP_1, TEST_ACTIVITY2) // 5
|
||||
.atWorkspace(0, 0, 6).putApp(PENDING_APP_1, TEST_ACTIVITY3) // 6
|
||||
.atWorkspace(0, 0, 7).putWidget(PENDING_APP_1, "pending.widget", 1, 1) // 7
|
||||
|
||||
.atWorkspace(0, 0, 8).putApp(PENDING_APP_2, TEST_ACTIVITY) // 8
|
||||
.atWorkspace(0, 0, 9).putApp(PENDING_APP_2, TEST_ACTIVITY2) // 9
|
||||
.atWorkspace(0, 0, 10).putApp(PENDING_APP_2, TEST_ACTIVITY3); // 10
|
||||
|
||||
mDownloadingApps = IntSet.wrap(4, 5, 6, 7, 8, 9, 10);
|
||||
mModelHelper.setupDefaultLayoutProvider(builder);
|
||||
mModelHelper.loadModelSync();
|
||||
assertEquals(10, mModelHelper.getBgDataModel().itemsIdMap.size());
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -47,36 +77,45 @@ public class PackageInstallStateChangedTaskTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionUpdate_ignore_installed() throws Exception {
|
||||
mModelHelper.executeTaskForTest(newTask("app1", 30));
|
||||
public void testSessionUpdate_ignore_installed() {
|
||||
// Run on model executor so that no other task runs in the middle.
|
||||
runOnExecutorSync(MODEL_EXECUTOR, () -> {
|
||||
mModelHelper.getModel().enqueueModelUpdateTask(newTask(TEST_PACKAGE, 30));
|
||||
|
||||
// No shortcuts were updated
|
||||
verifyProgressUpdate(0);
|
||||
// No shortcuts were updated
|
||||
verifyProgressUpdate(0);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionUpdate_shortcuts_updated() throws Exception {
|
||||
mModelHelper.executeTaskForTest(newTask("app3", 30));
|
||||
public void testSessionUpdate_shortcuts_updated() {
|
||||
// Run on model executor so that no other task runs in the middle.
|
||||
runOnExecutorSync(MODEL_EXECUTOR, () -> {
|
||||
mModelHelper.getModel().enqueueModelUpdateTask(newTask(PENDING_APP_1, 30));
|
||||
|
||||
verifyProgressUpdate(30, 5, 6, 7);
|
||||
verifyProgressUpdate(30, 4, 5, 6, 7);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionUpdate_widgets_updated() throws Exception {
|
||||
mModelHelper.executeTaskForTest(newTask("app4", 30));
|
||||
public void testSessionUpdate_widgets_updated() {
|
||||
// Run on model executor so that no other task runs in the middle.
|
||||
runOnExecutorSync(MODEL_EXECUTOR, () -> {
|
||||
mModelHelper.getModel().enqueueModelUpdateTask(newTask(PENDING_APP_2, 30));
|
||||
|
||||
verifyProgressUpdate(30, 8, 9);
|
||||
verifyProgressUpdate(30, 8, 9, 10);
|
||||
});
|
||||
}
|
||||
|
||||
private void verifyProgressUpdate(int progress, Integer... idsUpdated) {
|
||||
HashSet<Integer> updates = new HashSet<>(Arrays.asList(idsUpdated));
|
||||
private void verifyProgressUpdate(int progress, int... idsUpdated) {
|
||||
IntSet updates = IntSet.wrap(idsUpdated);
|
||||
for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
|
||||
if (info instanceof WorkspaceItemInfo) {
|
||||
assertEquals(updates.contains(info.id) ? progress: 100,
|
||||
((WorkspaceItemInfo) info).getProgressLevel());
|
||||
int expectedProgress = updates.contains(info.id) ? progress
|
||||
: (mDownloadingApps.contains(info.id) ? 0 : 100);
|
||||
if (info instanceof WorkspaceItemInfo wi) {
|
||||
assertEquals(expectedProgress, wi.getProgressLevel());
|
||||
} else {
|
||||
assertEquals(updates.contains(info.id) ? progress: -1,
|
||||
((LauncherAppWidgetInfo) info).installProgress);
|
||||
assertEquals(expectedProgress, ((LauncherAppWidgetInfo) info).installProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,9 +87,8 @@ class DisplayControllerTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(context.getObject(eq(WindowManagerProxy.INSTANCE), any()))
|
||||
.thenReturn(windowManagerProxy)
|
||||
whenever(context.getObject(eq(LauncherPrefs.INSTANCE), any())).thenReturn(launcherPrefs)
|
||||
whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
|
||||
whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
|
||||
|
||||
// Mock WindowManagerProxy
|
||||
val displayInfo =
|
||||
|
||||
@@ -15,34 +15,31 @@
|
||||
*/
|
||||
package com.android.launcher3.util;
|
||||
|
||||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||
import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTENT_URI;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.SessionParams;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
|
||||
import android.os.Process;
|
||||
import android.provider.Settings;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.util.ArrayMap;
|
||||
@@ -57,13 +54,10 @@ import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherModel.ModelUpdateTask;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.LauncherProvider;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.model.AllAppsList;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.model.ItemInstallQueue;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.pm.InstallSessionHelper;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.testing.TestInformationProvider;
|
||||
@@ -72,37 +66,25 @@ import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
|
||||
import com.android.launcher3.util.window.WindowManagerProxy;
|
||||
import com.android.launcher3.widget.custom.CustomWidgetManager;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Utility class to help manage Launcher Model and related objects for test.
|
||||
*/
|
||||
public class LauncherModelHelper {
|
||||
|
||||
public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
public static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
|
||||
public static final int APP_ICON = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||
public static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
|
||||
public static final int NO__ICON = -1;
|
||||
|
||||
public static final String TEST_PACKAGE = testContext().getPackageName();
|
||||
public static final String TEST_PACKAGE = getInstrumentation().getContext().getPackageName();
|
||||
public static final String TEST_ACTIVITY = "com.android.launcher3.tests.Activity2";
|
||||
public static final String TEST_ACTIVITY2 = "com.android.launcher3.tests.Activity3";
|
||||
public static final String TEST_ACTIVITY3 = "com.android.launcher3.tests.Activity4";
|
||||
|
||||
// Authority for providing a test default-workspace-layout data.
|
||||
private static final String TEST_PROVIDER_AUTHORITY =
|
||||
@@ -110,39 +92,18 @@ public class LauncherModelHelper {
|
||||
private static final int DEFAULT_BITMAP_SIZE = 10;
|
||||
private static final int DEFAULT_GRID_SIZE = 4;
|
||||
|
||||
private final HashMap<Class, HashMap<String, Field>> mFieldCache = new HashMap<>();
|
||||
private final MockContentResolver mMockResolver = new MockContentResolver();
|
||||
public final TestLauncherProvider provider;
|
||||
public final SandboxModelContext sandboxContext;
|
||||
|
||||
public final long defaultProfileId;
|
||||
private final RunnableList mDestroyTask = new RunnableList();
|
||||
|
||||
private BgDataModel mDataModel;
|
||||
private AllAppsList mAllAppsList;
|
||||
|
||||
public LauncherModelHelper() {
|
||||
Context context = getApplicationContext();
|
||||
// System settings cache content provider. Ensure that they are statically initialized
|
||||
Settings.Secure.getString(context.getContentResolver(), "test");
|
||||
Settings.System.getString(context.getContentResolver(), "test");
|
||||
Settings.Global.getString(context.getContentResolver(), "test");
|
||||
|
||||
provider = new TestLauncherProvider();
|
||||
sandboxContext = new SandboxModelContext();
|
||||
defaultProfileId = UserCache.INSTANCE.get(sandboxContext)
|
||||
.getSerialNumberForUser(Process.myUserHandle());
|
||||
setupProvider(LauncherProvider.AUTHORITY, provider);
|
||||
}
|
||||
|
||||
public void setupProvider(String authority, ContentProvider provider) {
|
||||
ProviderInfo providerInfo = new ProviderInfo();
|
||||
providerInfo.authority = authority;
|
||||
providerInfo.applicationInfo = sandboxContext.getApplicationInfo();
|
||||
provider.attachInfo(sandboxContext, providerInfo);
|
||||
mMockResolver.addProvider(providerInfo.authority, provider);
|
||||
doReturn(providerInfo)
|
||||
.when(sandboxContext.mPm)
|
||||
.resolveContentProvider(eq(authority), anyInt());
|
||||
sandboxContext.setupProvider(authority, provider);
|
||||
}
|
||||
|
||||
public LauncherModel getModel() {
|
||||
@@ -151,16 +112,35 @@ public class LauncherModelHelper {
|
||||
|
||||
public synchronized BgDataModel getBgDataModel() {
|
||||
if (mDataModel == null) {
|
||||
mDataModel = ReflectionHelpers.getField(getModel(), "mBgDataModel");
|
||||
getModel().enqueueModelUpdateTask(new ModelUpdateTask() {
|
||||
@Override
|
||||
public void init(@NonNull LauncherAppState app, @NonNull LauncherModel model,
|
||||
@NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList,
|
||||
@NonNull Executor uiExecutor) {
|
||||
mDataModel = dataModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() { }
|
||||
});
|
||||
}
|
||||
return mDataModel;
|
||||
}
|
||||
|
||||
public synchronized AllAppsList getAllAppsList() {
|
||||
if (mAllAppsList == null) {
|
||||
mAllAppsList = ReflectionHelpers.getField(getModel(), "mBgAllAppsList");
|
||||
}
|
||||
return mAllAppsList;
|
||||
/**
|
||||
* Creates a installer session for the provided package.
|
||||
*/
|
||||
public int createInstallerSession(String pkg) throws IOException {
|
||||
SessionParams sp = new SessionParams(MODE_FULL_INSTALL);
|
||||
sp.setAppPackageName(pkg);
|
||||
Bitmap icon = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
|
||||
icon.eraseColor(Color.RED);
|
||||
sp.setAppIcon(icon);
|
||||
sp.setAppLabel(pkg);
|
||||
PackageInstaller pi = sandboxContext.getPackageManager().getPackageInstaller();
|
||||
int sessionId = pi.createSession(sp);
|
||||
mDestroyTask.add(() -> pi.abandonSession(sessionId));
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
@@ -175,6 +155,8 @@ public class LauncherModelHelper {
|
||||
waitOrThrow(l1);
|
||||
sandboxContext.onDestroy();
|
||||
l2.countDown();
|
||||
|
||||
mDestroyTask.executeAllAndDestroy();
|
||||
}
|
||||
|
||||
private void waitOrThrow(CountDownLatch latch) {
|
||||
@@ -185,184 +167,6 @@ public class LauncherModelHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously executes the task and returns all the UI callbacks posted.
|
||||
*/
|
||||
public List<Runnable> executeTaskForTest(ModelUpdateTask task) throws Exception {
|
||||
LauncherModel model = getModel();
|
||||
if (!model.isModelLoaded()) {
|
||||
ReflectionHelpers.setField(model, "mModelLoaded", true);
|
||||
}
|
||||
Executor mockExecutor = mock(Executor.class);
|
||||
model.enqueueModelUpdateTask(new ModelUpdateTask() {
|
||||
@Override
|
||||
public void init(@NonNull final LauncherAppState app,
|
||||
@NonNull final LauncherModel model, @NonNull final BgDataModel dataModel,
|
||||
@NonNull final AllAppsList allAppsList, @NonNull final Executor uiExecutor) {
|
||||
task.init(app, model, dataModel, allAppsList, mockExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
task.run();
|
||||
}
|
||||
});
|
||||
MODEL_EXECUTOR.submit(() -> null).get();
|
||||
|
||||
ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
|
||||
verify(mockExecutor, atLeast(0)).execute(captor.capture());
|
||||
return captor.getAllValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously executes a task on the model
|
||||
*/
|
||||
public <T> T executeSimpleTask(Function<BgDataModel, T> task) throws Exception {
|
||||
BgDataModel dataModel = getBgDataModel();
|
||||
return MODEL_EXECUTOR.submit(() -> task.apply(dataModel)).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes mock data for the test.
|
||||
*/
|
||||
public void initializeData(String resourceName) throws Exception {
|
||||
BgDataModel bgDataModel = getBgDataModel();
|
||||
AllAppsList allAppsList = getAllAppsList();
|
||||
|
||||
MODEL_EXECUTOR.submit(() -> {
|
||||
// Copy apk from resources to a local file and install from there.
|
||||
Resources resources = testContext().getResources();
|
||||
int resId = resources.getIdentifier(
|
||||
resourceName, "raw", testContext().getPackageName());
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
resources.openRawResource(resId)))) {
|
||||
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(sandboxContext,
|
||||
(ItemInfo) initItem(classMap.get(commands[1]), commands, 2),
|
||||
false);
|
||||
break;
|
||||
case "allApps":
|
||||
allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1), null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).get();
|
||||
}
|
||||
|
||||
private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
|
||||
HashMap<String, Field> cache = mFieldCache.get(clazz);
|
||||
if (cache == null) {
|
||||
cache = new HashMap<>();
|
||||
Class c = clazz;
|
||||
while (c != null) {
|
||||
for (Field f : c.getDeclaredFields()) {
|
||||
f.setAccessible(true);
|
||||
cache.put(f.getName(), f);
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
mFieldCache.put(clazz, cache);
|
||||
}
|
||||
|
||||
Object item = clazz.newInstance();
|
||||
for (int i = startIndex; i < fieldDef.length; i++) {
|
||||
String[] fieldData = fieldDef[i].split("=", 2);
|
||||
Field f = cache.get(fieldData[0]);
|
||||
Class type = f.getType();
|
||||
if (type == int.class || type == long.class) {
|
||||
f.set(item, Integer.parseInt(fieldData[1]));
|
||||
} else if (type == CharSequence.class || type == String.class) {
|
||||
f.set(item, fieldData[1]);
|
||||
} else if (type == Intent.class) {
|
||||
if (!fieldData[1].startsWith("#Intent")) {
|
||||
fieldData[1] = "#Intent;" + fieldData[1] + ";end";
|
||||
}
|
||||
f.set(item, Intent.parseUri(fieldData[1], 0));
|
||||
} else if (type == ComponentName.class) {
|
||||
f.set(item, ComponentName.unflattenFromString(fieldData[1]));
|
||||
} else {
|
||||
throw new Exception("Added parsing logic for "
|
||||
+ f.getName() + " of type " + f.getType());
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public int addItem(int type, int screen, int container, int x, int y) {
|
||||
return addItem(type, screen, container, x, y, defaultProfileId, TEST_PACKAGE);
|
||||
}
|
||||
|
||||
public int addItem(int type, int screen, int container, int x, int y, long profileId) {
|
||||
return addItem(type, screen, container, x, y, profileId, TEST_PACKAGE);
|
||||
}
|
||||
|
||||
public int addItem(int type, int screen, int container, int x, int y, String packageName) {
|
||||
return addItem(type, screen, container, x, y, defaultProfileId, packageName);
|
||||
}
|
||||
|
||||
public int addItem(int type, int screen, int container, int x, int y, String packageName,
|
||||
int id, Uri contentUri) {
|
||||
addItem(type, screen, container, x, y, defaultProfileId, packageName, id, contentUri);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mock 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, long profileId,
|
||||
String packageName) {
|
||||
int id = LauncherSettings.Settings.call(sandboxContext.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
|
||||
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
|
||||
addItem(type, screen, container, x, y, profileId, packageName, id, CONTENT_URI);
|
||||
return id;
|
||||
}
|
||||
|
||||
public void addItem(int type, int screen, int container, int x, int y, long profileId,
|
||||
String packageName, int id, Uri contentUri) {
|
||||
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);
|
||||
values.put(LauncherSettings.Favorites.PROFILE_ID, profileId);
|
||||
|
||||
if (type == APP_ICON || type == SHORTCUT) {
|
||||
values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
|
||||
values.put(LauncherSettings.Favorites.INTENT,
|
||||
new Intent(Intent.ACTION_MAIN).setPackage(packageName).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, profileId);
|
||||
}
|
||||
}
|
||||
|
||||
sandboxContext.getContentResolver().insert(contentUri, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a mock provider to load the provided layout by default, next time the layout loads
|
||||
*/
|
||||
@@ -394,6 +198,9 @@ public class LauncherModelHelper {
|
||||
}
|
||||
};
|
||||
setupProvider(TEST_PROVIDER_AUTHORITY, cp);
|
||||
mDestroyTask.add(() -> runOnExecutorSync(MODEL_EXECUTOR, () ->
|
||||
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
|
||||
"settings delete secure launcher3.layout.provider")));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -402,46 +209,16 @@ public class LauncherModelHelper {
|
||||
*/
|
||||
public void loadModelSync() throws ExecutionException, InterruptedException {
|
||||
Callbacks mockCb = new Callbacks() { };
|
||||
Executors.MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get();
|
||||
MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get();
|
||||
|
||||
Executors.MODEL_EXECUTOR.submit(() -> { }).get();
|
||||
Executors.MAIN_EXECUTOR.submit(() -> { }).get();
|
||||
Executors.MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
|
||||
MAIN_EXECUTOR.submit(() -> { }).get();
|
||||
MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
|
||||
}
|
||||
|
||||
/**
|
||||
* An extension of LauncherProvider backed up by in-memory database.
|
||||
*/
|
||||
public static class TestLauncherProvider extends LauncherProvider {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public SQLiteDatabase getDb() {
|
||||
return getModelDbController().getDb();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deleteContents(File dir) {
|
||||
File[] files = dir.listFiles();
|
||||
boolean success = true;
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
success &= deleteContents(file);
|
||||
}
|
||||
if (!file.delete()) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public class SandboxModelContext extends SandboxContext {
|
||||
public static class SandboxModelContext extends SandboxContext {
|
||||
|
||||
private final MockContentResolver mMockResolver = new MockContentResolver();
|
||||
private final ArrayMap<String, Object> mSpiedServices = new ArrayMap<>();
|
||||
private final PackageManager mPm;
|
||||
private final File mDbDir;
|
||||
@@ -453,8 +230,31 @@ public class LauncherModelHelper {
|
||||
DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
|
||||
SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, LockedUserState.INSTANCE,
|
||||
ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
|
||||
|
||||
// System settings cache content provider. Ensure that they are statically initialized
|
||||
Settings.Secure.getString(
|
||||
ApplicationProvider.getApplicationContext().getContentResolver(), "test");
|
||||
Settings.System.getString(
|
||||
ApplicationProvider.getApplicationContext().getContentResolver(), "test");
|
||||
Settings.Global.getString(
|
||||
ApplicationProvider.getApplicationContext().getContentResolver(), "test");
|
||||
|
||||
mPm = spy(getBaseContext().getPackageManager());
|
||||
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
|
||||
setupProvider(LauncherProvider.AUTHORITY, new LauncherProvider() {
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> T createObject(MainThreadInitializedObject<T> object) {
|
||||
if (object == LauncherAppState.INSTANCE) {
|
||||
return (T) new LauncherAppState(this, null /* iconCacheFileName */);
|
||||
}
|
||||
return super.createObject(object);
|
||||
}
|
||||
|
||||
public SandboxModelContext allow(MainThreadInitializedObject object) {
|
||||
@@ -505,9 +305,30 @@ public class LauncherModelHelper {
|
||||
mSpiedServices.put(name, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static Context testContext() {
|
||||
return getInstrumentation().getContext();
|
||||
public void setupProvider(String authority, ContentProvider provider) {
|
||||
ProviderInfo providerInfo = new ProviderInfo();
|
||||
providerInfo.authority = authority;
|
||||
providerInfo.applicationInfo = getApplicationInfo();
|
||||
provider.attachInfo(this, providerInfo);
|
||||
mMockResolver.addProvider(providerInfo.authority, provider);
|
||||
doReturn(providerInfo).when(mPm).resolveContentProvider(eq(authority), anyInt());
|
||||
}
|
||||
|
||||
private static boolean deleteContents(File dir) {
|
||||
File[] files = dir.listFiles();
|
||||
boolean success = true;
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
success &= deleteContents(file);
|
||||
}
|
||||
if (!file.delete()) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
@@ -154,6 +155,30 @@ public class TestUtil {
|
||||
Settings.Secure.putString(context.getContentResolver(), LAYOUT_DIGEST_KEY, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to run a task synchronously which converts any exceptions to RuntimeException
|
||||
*/
|
||||
public static void runOnExecutorSync(ExecutorService executor, UncheckedRunnable task) {
|
||||
try {
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
task.run();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Interface to indicate a runnable which can throw any exception. */
|
||||
public interface UncheckedRunnable {
|
||||
/** Method to run the task */
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
|
||||
private static class PackageInstallCheck extends LauncherApps.Callback
|
||||
implements AutoCloseable {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user