From 98c3cafcafb8ff769ebf8ede86e3bb4b09a44941 Mon Sep 17 00:00:00 2001 From: Charlie Anderson Date: Tue, 16 Jul 2024 18:37:14 -0400 Subject: [PATCH] [Test Week] Add tests for each operation in PackageUpdatedTask Bug:353303621 Test: unit test Flag: TEST_ONLY Change-Id: Ic2194fd1506c4ff5a542eac62a492f395d12886e --- .../launcher3/model/PackageUpdatedTask.java | 96 +++---- .../launcher3/model/PackageUpdatedTaskTest.kt | 235 ++++++++++++++++++ 2 files changed, 283 insertions(+), 48 deletions(-) create mode 100644 tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 079987b0b5..2febb22c4f 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -109,7 +109,7 @@ public class PackageUpdatedTask implements ModelUpdateTask { final IconCache iconCache = app.getIconCache(); final String[] packages = mPackages; - final int N = packages.length; + final int packageCount = packages.length; final FlagOp flagOp; final HashSet packageSet = new HashSet<>(Arrays.asList(packages)); final Predicate matcher = mOp == OP_USER_AVAILABILITY_CHANGE @@ -123,7 +123,7 @@ public class PackageUpdatedTask implements ModelUpdateTask { } switch (mOp) { case OP_ADD: { - for (int i = 0; i < N; i++) { + for (int i = 0; i < packageCount; i++) { iconCache.updateIconsForPkg(packages[i], mUser); if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) { if (DEBUG) { @@ -146,7 +146,7 @@ public class PackageUpdatedTask implements ModelUpdateTask { + " Look for earlier AllAppsList logs to find more information."); removedComponents.add(a.componentName); })) { - for (int i = 0; i < N; i++) { + for (int i = 0; i < packageCount; i++) { iconCache.updateIconsForPkg(packages[i], mUser); activitiesLists.put(packages[i], appsList.updatePackage(context, packages[i], mUser)); @@ -156,13 +156,13 @@ public class PackageUpdatedTask implements ModelUpdateTask { flagOp = FlagOp.NO_OP.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE); break; case OP_REMOVE: { - for (int i = 0; i < N; i++) { + for (int i = 0; i < packageCount; i++) { iconCache.removeIconsForPkg(packages[i], mUser); } // Fall through } case OP_UNAVAILABLE: - for (int i = 0; i < N; i++) { + for (int i = 0; i < packageCount; i++) { if (DEBUG) { Log.d(TAG, getOpString() + ": removing package=" + packages[i]); } @@ -217,44 +217,44 @@ public class PackageUpdatedTask implements ModelUpdateTask { // For system apps, package manager send OP_UPDATE when an app is enabled. final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE; synchronized (dataModel) { - dataModel.forAllWorkspaceItemInfos(mUser, si -> { + dataModel.forAllWorkspaceItemInfos(mUser, itemInfo -> { boolean infoUpdated = false; boolean shortcutUpdated = false; - ComponentName cn = si.getTargetComponent(); - if (cn != null && matcher.test(si)) { + ComponentName cn = itemInfo.getTargetComponent(); + if (cn != null && matcher.test(itemInfo)) { String packageName = cn.getPackageName(); - if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) { - forceKeepShortcuts.add(si.id); + if (itemInfo.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) { + forceKeepShortcuts.add(itemInfo.id); if (mOp == OP_REMOVE) { return; } } - if (si.isPromise() && isNewApkAvailable) { + if (itemInfo.isPromise() && isNewApkAvailable) { boolean isTargetValid = !cn.getClassName().equals( IconCache.EMPTY_CLASS_NAME); - if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + if (itemInfo.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { List shortcut = new ShortcutRequest(context, mUser) .forPackage(cn.getPackageName(), - si.getDeepShortcutId()) + itemInfo.getDeepShortcutId()) .query(ShortcutRequest.PINNED); if (shortcut.isEmpty()) { isTargetValid = false; if (DEBUG) { Log.d(TAG, "Pinned Shortcut not found for updated" - + " package=" + si.getTargetPackage()); + + " package=" + itemInfo.getTargetPackage()); } } else { if (DEBUG) { Log.d(TAG, "Found pinned shortcut for updated" - + " package=" + si.getTargetPackage() + + " package=" + itemInfo.getTargetPackage() + ", isTargetValid=" + isTargetValid); } - si.updateFromDeepShortcutInfo(shortcut.get(0), context); + itemInfo.updateFromDeepShortcutInfo(shortcut.get(0), context); infoUpdated = true; } } else if (isTargetValid) { @@ -262,39 +262,39 @@ public class PackageUpdatedTask implements ModelUpdateTask { .isActivityEnabled(cn, mUser); } - if (!isTargetValid && (si.hasStatusFlag( + if (!isTargetValid && (itemInfo.hasStatusFlag( FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON) - || si.isArchived())) { - if (updateWorkspaceItemIntent(context, si, packageName)) { + || itemInfo.isArchived())) { + if (updateWorkspaceItemIntent(context, itemInfo, packageName)) { infoUpdated = true; - } else if (si.hasPromiseIconUi()) { - removedShortcuts.add(si.id); + } else if (itemInfo.hasPromiseIconUi()) { + removedShortcuts.add(itemInfo.id); if (DEBUG) { FileLog.w(TAG, "Removing restored shortcut promise icon" + " that no longer points to valid component." - + " id=" + si.id - + ", package=" + si.getTargetPackage() - + ", status=" + si.status - + ", isArchived=" + si.isArchived()); + + " id=" + itemInfo.id + + ", package=" + itemInfo.getTargetPackage() + + ", status=" + itemInfo.status + + ", isArchived=" + itemInfo.isArchived()); } return; } } else if (!isTargetValid) { - removedShortcuts.add(si.id); + removedShortcuts.add(itemInfo.id); if (DEBUG) { FileLog.w(TAG, "Removing shortcut that no longer points to" + " valid component." - + " id=" + si.id - + " package=" + si.getTargetPackage() - + " status=" + si.status); + + " id=" + itemInfo.id + + " package=" + itemInfo.getTargetPackage() + + " status=" + itemInfo.status); } return; } else { - si.status = WorkspaceItemInfo.DEFAULT; + itemInfo.status = WorkspaceItemInfo.DEFAULT; infoUpdated = true; } } else if (isNewApkAvailable && removedComponents.contains(cn)) { - if (updateWorkspaceItemIntent(context, si, packageName)) { + if (updateWorkspaceItemIntent(context, itemInfo, packageName)) { infoUpdated = true; } } @@ -304,7 +304,7 @@ public class PackageUpdatedTask implements ModelUpdateTask { packageName); // TODO: See if we can migrate this to // AppInfo#updateRuntimeFlagsForActivityTarget - si.setProgressLevel( + itemInfo.setProgressLevel( activities == null || activities.isEmpty() ? 100 : PackageManagerHelper.getLoadingProgress( @@ -313,42 +313,42 @@ public class PackageUpdatedTask implements ModelUpdateTask { // In case an app is archived, we need to make sure that archived state // in WorkspaceItemInfo is refreshed. if (Flags.enableSupportForArchiving() && !activities.isEmpty()) { - boolean newArchivalState = activities.get( - 0).getActivityInfo().isArchived; - if (newArchivalState != si.isArchived()) { - si.runtimeStatusFlags ^= FLAG_ARCHIVED; + boolean newArchivalState = activities.get(0) + .getActivityInfo().isArchived; + if (newArchivalState != itemInfo.isArchived()) { + itemInfo.runtimeStatusFlags ^= FLAG_ARCHIVED; infoUpdated = true; } } - if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) { + if (itemInfo.itemType == Favorites.ITEM_TYPE_APPLICATION) { if (activities != null && !activities.isEmpty()) { - si.setNonResizeable(ApiWrapper.INSTANCE.get(context) + itemInfo.setNonResizeable(ApiWrapper.INSTANCE.get(context) .isNonResizeableActivity(activities.get(0))); } - iconCache.getTitleAndIcon(si, si.usingLowResIcon()); + iconCache.getTitleAndIcon(itemInfo, itemInfo.usingLowResIcon()); infoUpdated = true; } } - int oldRuntimeFlags = si.runtimeStatusFlags; - si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags); - if (si.runtimeStatusFlags != oldRuntimeFlags) { + int oldRuntimeFlags = itemInfo.runtimeStatusFlags; + itemInfo.runtimeStatusFlags = flagOp.apply(itemInfo.runtimeStatusFlags); + if (itemInfo.runtimeStatusFlags != oldRuntimeFlags) { shortcutUpdated = true; } } if (infoUpdated || shortcutUpdated) { - updatedWorkspaceItems.add(si); + updatedWorkspaceItems.add(itemInfo); } - if (infoUpdated && si.id != ItemInfo.NO_ID) { - taskController.getModelWriter().updateItemInDatabase(si); + if (infoUpdated && itemInfo.id != ItemInfo.NO_ID) { + taskController.getModelWriter().updateItemInDatabase(itemInfo); } }); for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) { if (mUser.equals(widgetInfo.user) && widgetInfo.hasRestoreFlag( - LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) + LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && packageSet.contains(widgetInfo.providerName.getPackageName())) { widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY @@ -391,7 +391,7 @@ public class PackageUpdatedTask implements ModelUpdateTask { } else if (mOp == OP_UPDATE) { // Mark disabled packages in the broadcast to be removed final LauncherApps launcherApps = context.getSystemService(LauncherApps.class); - for (int i=0; i() + private val expectedWorkspaceItem = spy(WorkspaceItemInfo()) + + private val mockIconCache: IconCache = mock() + private val mockTaskController: ModelTaskController = mock() + private val mockAppFilter: AppFilter = mock() + private val mockApplicationInfo: ApplicationInfo = mock() + private val mockActivityInfo: ActivityInfo = mock() + + private lateinit var mAllAppsList: AllAppsList + + @Before + fun setup() { + mAllAppsList = spy(AllAppsList(mockIconCache, mockAppFilter)) + mLauncherModelHelper.sandboxContext.spyService(LauncherApps::class.java).apply { + whenever(getActivityList(expectedPackage, mUser)) + .thenReturn(listOf(expectedActivityInfo)) + } + whenever(mAppState.iconCache).thenReturn(mockIconCache) + whenever(mockTaskController.app).thenReturn(mAppState) + whenever(mockAppFilter.shouldShowApp(expectedComponent)).thenReturn(true) + mockApplicationInfo.apply { + uid = 1 + isArchived = false + } + mockActivityInfo.isArchived = false + expectedActivityInfo.apply { + whenever(applicationInfo).thenReturn(mockApplicationInfo) + whenever(activityInfo).thenReturn(mockActivityInfo) + whenever(componentName).thenReturn(expectedComponent) + } + expectedWorkspaceItem.apply { + itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION + container = LauncherSettings.Favorites.CONTAINER_DESKTOP + user = mUser + whenever(targetPackage).thenReturn(expectedPackage) + whenever(targetComponent).thenReturn(expectedComponent) + } + } + + @After + fun tearDown() { + mLauncherModelHelper.destroy() + } + + @Test + fun `OP_ADD triggers model callbacks and adds new items to AllAppsList`() { + // Given + val taskUnderTest = PackageUpdatedTask(OP_ADD, mUser, expectedPackage) + // When + mDataModel.addItem(mContext, expectedWorkspaceItem, true) + TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { + taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList) + } + mLauncherModelHelper.loadModelSync() + // Then + verify(mockIconCache).updateIconsForPkg(expectedPackage, mUser) + verify(mAllAppsList).addPackage(mContext, expectedPackage, mUser) + verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem)) + verify(mockTaskController).bindUpdatedWidgets(mDataModel) + assertThat(mAllAppsList.data.firstOrNull()?.componentName) + .isEqualTo(AppInfo(mContext, expectedActivityInfo, mUser).componentName) + } + + @Test + fun `OP_UPDATE triggers model callbacks and updates items in AllAppsList`() { + // Given + val taskUnderTest = PackageUpdatedTask(OP_UPDATE, mUser, expectedPackage) + // When + mDataModel.addItem(mContext, expectedWorkspaceItem, true) + TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { + taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList) + } + mLauncherModelHelper.loadModelSync() + // Then + verify(mockIconCache).updateIconsForPkg(expectedPackage, mUser) + verify(mAllAppsList).updatePackage(mContext, expectedPackage, mUser) + verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem)) + assertThat(mAllAppsList.data.firstOrNull()?.componentName) + .isEqualTo(AppInfo(mContext, expectedActivityInfo, mUser).componentName) + } + + @Test + fun `OP_REMOVE triggers model callbacks and removes packages and icons`() { + // Given + val taskUnderTest = PackageUpdatedTask(OP_REMOVE, mUser, expectedPackage) + // When + mDataModel.addItem(mContext, expectedWorkspaceItem, true) + TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { + taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList) + } + mLauncherModelHelper.loadModelSync() + // Then + verify(mockIconCache).removeIconsForPkg(expectedPackage, mUser) + verify(mAllAppsList).removePackage(expectedPackage, mUser) + verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem)) + assertThat(mAllAppsList.data).isEmpty() + } + + @Test + fun `OP_UNAVAILABLE triggers model callbacks and removes package from AllAppsList`() { + // Given + val taskUnderTest = PackageUpdatedTask(OP_UNAVAILABLE, mUser, expectedPackage) + // When + mDataModel.addItem(mContext, expectedWorkspaceItem, true) + TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { + taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList) + } + mLauncherModelHelper.loadModelSync() + // Then + verify(mAllAppsList).removePackage(expectedPackage, mUser) + verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem)) + assertThat(mAllAppsList.data).isEmpty() + } + + @Test + fun `OP_SUSPEND triggers model callbacks and updates flags in AllAppsList`() { + // Given + val taskUnderTest = PackageUpdatedTask(OP_SUSPEND, mUser, expectedPackage) + // When + mDataModel.addItem(mContext, expectedWorkspaceItem, true) + mAllAppsList.add(AppInfo(mContext, expectedActivityInfo, mUser), expectedActivityInfo) + TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { + taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList) + } + mLauncherModelHelper.loadModelSync() + // Then + verify(mAllAppsList).updateDisabledFlags(any(), any()) + verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem)) + assertThat(mAllAppsList.getAndResetChangeFlag()).isTrue() + } + + @Test + fun `OP_UNSUSPEND triggers no callbacks when app not suspended`() { + // Given + val taskUnderTest = PackageUpdatedTask(OP_UNSUSPEND, mUser, expectedPackage) + // When + mDataModel.addItem(mContext, expectedWorkspaceItem, true) + TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { + taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList) + } + mLauncherModelHelper.loadModelSync() + // Then + verify(mAllAppsList).updateDisabledFlags(any(), any()) + verify(mockTaskController).bindUpdatedWorkspaceItems(emptyList()) + assertThat(mAllAppsList.getAndResetChangeFlag()).isFalse() + } + + @EnableFlags(FLAG_ENABLE_PRIVATE_SPACE) + @Test + fun `OP_USER_AVAILABILITY_CHANGE triggers no callbacks if current user not work or private`() { + // Given + val taskUnderTest = PackageUpdatedTask(OP_USER_AVAILABILITY_CHANGE, mUser, expectedPackage) + // When + mDataModel.addItem(mContext, expectedWorkspaceItem, true) + TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) { + taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList) + } + mLauncherModelHelper.loadModelSync() + // Then + verify(mAllAppsList).updateDisabledFlags(any(), any()) + verify(mockTaskController).bindUpdatedWorkspaceItems(emptyList()) + assertThat(mAllAppsList.data).isEmpty() + } +}