[Test Week] Add tests for each operation in PackageUpdatedTask
Bug:353303621
Test: unit test
Flag: TEST_ONLY
Change-Id: Ic2194fd1506c4ff5a542eac62a492f395d12886e
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 079987b..2febb22 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -109,7 +109,7 @@
final IconCache iconCache = app.getIconCache();
final String[] packages = mPackages;
- final int N = packages.length;
+ final int packageCount = packages.length;
final FlagOp flagOp;
final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
final Predicate<ItemInfo> matcher = mOp == OP_USER_AVAILABILITY_CHANGE
@@ -123,7 +123,7 @@
}
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 @@
+ " 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 @@
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 @@
// 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<ShortcutInfo> 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 @@
.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 @@
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 @@
// 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 @@
} 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<N; i++) {
+ for (int i = 0; i < packageCount; i++) {
if (!launcherApps.isPackageEnabled(packages[i], mUser)) {
if (DEBUG) {
Log.d(TAG, "OP_UPDATE:"
@@ -423,7 +423,7 @@
if (mOp == OP_ADD) {
// Load widgets for the new package. Changes due to app updates are handled through
// AppWidgetHost events, this is just to initialize the long-press options.
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < packageCount; i++) {
dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
}
taskController.bindUpdatedWidgets(dataModel);
diff --git a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
new file mode 100644
index 0000000..d9af07a
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.LauncherActivityInfo
+import android.content.pm.LauncherApps
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.AppFilter
+import com.android.launcher3.Flags.FLAG_ENABLE_PRIVATE_SPACE
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.PackageUpdatedTask.OP_ADD
+import com.android.launcher3.model.PackageUpdatedTask.OP_REMOVE
+import com.android.launcher3.model.PackageUpdatedTask.OP_SUSPEND
+import com.android.launcher3.model.PackageUpdatedTask.OP_UNAVAILABLE
+import com.android.launcher3.model.PackageUpdatedTask.OP_UNSUSPEND
+import com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE
+import com.android.launcher3.model.PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.TestUtil
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class PackageUpdatedTaskTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val mUser = UserHandle(0)
+ private val mDataModel: BgDataModel = BgDataModel()
+ private val mLauncherModelHelper = LauncherModelHelper()
+ private val mContext: SandboxModelContext = spy(mLauncherModelHelper.sandboxContext)
+ private val mAppState: LauncherAppState = spy(LauncherAppState.getInstance(mContext))
+
+ private val expectedPackage = "Test.Package"
+ private val expectedComponent = ComponentName(expectedPackage, "TestClass")
+ private val expectedActivityInfo: LauncherActivityInfo = mock<LauncherActivityInfo>()
+ private val expectedWorkspaceItem = spy(WorkspaceItemInfo())
+
+ private val mockIconCache: IconCache = mock()
+ private val mockTaskController: ModelTaskController = mock<ModelTaskController>()
+ private val mockAppFilter: AppFilter = mock<AppFilter>()
+ private val mockApplicationInfo: ApplicationInfo = mock<ApplicationInfo>()
+ private val mockActivityInfo: ActivityInfo = mock<ActivityInfo>()
+
+ 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()
+ }
+}