Loading src/com/android/launcher3/model/PackageUpdatedTask.java +48 −48 Original line number Diff line number Diff line Loading @@ -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<String> packageSet = new HashSet<>(Arrays.asList(packages)); final Predicate<ItemInfo> matcher = mOp == OP_USER_AVAILABILITY_CHANGE Loading @@ -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) { Loading @@ -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)); Loading @@ -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]); } Loading Loading @@ -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<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) { Loading @@ -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; } } Loading @@ -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( Loading @@ -313,35 +313,35 @@ 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); } }); Loading Loading @@ -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<N; i++) { for (int i = 0; i < packageCount; i++) { if (!launcherApps.isPackageEnabled(packages[i], mUser)) { if (DEBUG) { Log.d(TAG, "OP_UPDATE:" Loading Loading @@ -423,7 +423,7 @@ public class PackageUpdatedTask implements ModelUpdateTask { 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); Loading tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt 0 → 100644 +235 −0 Original line number Diff line number Diff line /* * 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() } } Loading
src/com/android/launcher3/model/PackageUpdatedTask.java +48 −48 Original line number Diff line number Diff line Loading @@ -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<String> packageSet = new HashSet<>(Arrays.asList(packages)); final Predicate<ItemInfo> matcher = mOp == OP_USER_AVAILABILITY_CHANGE Loading @@ -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) { Loading @@ -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)); Loading @@ -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]); } Loading Loading @@ -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<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) { Loading @@ -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; } } Loading @@ -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( Loading @@ -313,35 +313,35 @@ 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); } }); Loading Loading @@ -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<N; i++) { for (int i = 0; i < packageCount; i++) { if (!launcherApps.isPackageEnabled(packages[i], mUser)) { if (DEBUG) { Log.d(TAG, "OP_UPDATE:" Loading Loading @@ -423,7 +423,7 @@ public class PackageUpdatedTask implements ModelUpdateTask { 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); Loading
tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt 0 → 100644 +235 −0 Original line number Diff line number Diff line /* * 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() } }