diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index ddc775dd9c..eab28b740d 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -46,6 +46,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.BuildConfig; import com.android.launcher3.Workspace; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.CollectionInfo; import com.android.launcher3.model.data.ItemInfo; @@ -210,7 +211,7 @@ public class BgDataModel { } /** - * Updates the deep shortucts state in system to match out internal model, pinning any missing + * Updates the deep shortcuts state in system to match out internal model, pinning any missing * shortcuts and unpinning any extra shortcuts. */ public void updateShortcutPinnedState(Context context) { @@ -266,6 +267,8 @@ public class BgDataModel { || !systemShortcuts.containsAll(modelShortcuts)) { // Update system state for this package try { + FileLog.d(TAG, "updateShortcutPinnedState:" + + " Pinning Shortcuts: " + entry.getKey() + ": " + modelShortcuts); context.getSystemService(LauncherApps.class).pinShortcuts( entry.getKey(), new ArrayList<>(modelShortcuts), user); } catch (SecurityException | IllegalStateException e) { @@ -278,6 +281,9 @@ public class BgDataModel { systemMap.keySet().forEach(packageName -> { // Update system state try { + FileLog.d(TAG, "updateShortcutPinnedState:" + + " Unpinning extra Shortcuts for package: " + packageName + + ": " + systemMap.get(packageName)); context.getSystemService(LauncherApps.class).pinShortcuts( packageName, Collections.emptyList(), user); } catch (SecurityException | IllegalStateException e) { diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 6a8d86bd0b..bd8c36b576 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -24,6 +24,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SH import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET; import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; +import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED; import android.content.ComponentName; import android.content.ContentValues; @@ -307,7 +308,7 @@ public class LoaderCursor extends CursorWrapper { * Make an WorkspaceItemInfo object for a restored application or shortcut item that points * to a package that is not yet installed on the system. */ - public WorkspaceItemInfo getRestoredItemInfo(Intent intent) { + public WorkspaceItemInfo getRestoredItemInfo(Intent intent, boolean isArchived) { final WorkspaceItemInfo info = new WorkspaceItemInfo(); info.user = user; info.intent = intent; @@ -317,7 +318,7 @@ public class LoaderCursor extends CursorWrapper { mIconCache.getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG); } - if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON)) { + if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON) || isArchived) { String title = getTitle(); if (!TextUtils.isEmpty(title)) { info.title = Utilities.trim(title); @@ -333,6 +334,7 @@ public class LoaderCursor extends CursorWrapper { info.contentDescription = mIconCache.getUserBadgedLabel(info.title, info.user); info.itemType = itemType; info.status = restoreFlag; + if (isArchived) info.runtimeStatusFlags |= FLAG_ARCHIVED; return info; } diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 6bef292841..d1eceb905a 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.model; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED; @@ -39,7 +40,6 @@ import androidx.annotation.NonNull; import com.android.launcher3.Flags; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel.ModelUpdateTask; -import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.IconCache; @@ -238,19 +238,22 @@ public class PackageUpdatedTask implements ModelUpdateTask { if (itemInfo.isPromise() && isNewApkAvailable) { boolean isTargetValid = !cn.getClassName().equals( IconCache.EMPTY_CLASS_NAME); - if (itemInfo.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + if (itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) { List shortcut = new ShortcutRequest(context, mUser) .forPackage(cn.getPackageName(), itemInfo.getDeepShortcutId()) .query(ShortcutRequest.PINNED); - if (shortcut.isEmpty()) { + if (shortcut.isEmpty() + && !(Flags.restoreArchivedShortcuts() + && !itemInfo.isArchived()) + ) { isTargetValid = false; if (DEBUG) { Log.d(TAG, "Pinned Shortcut not found for updated" + " package=" + itemInfo.getTargetPackage()); } - } else { + } else if (!shortcut.isEmpty()) { if (DEBUG) { Log.d(TAG, "Found pinned shortcut for updated" + " package=" + itemInfo.getTargetPackage() @@ -269,7 +272,7 @@ public class PackageUpdatedTask implements ModelUpdateTask { || itemInfo.isArchived())) { if (updateWorkspaceItemIntent(context, itemInfo, packageName)) { infoUpdated = true; - } else if (itemInfo.hasPromiseIconUi()) { + } else if (shouldRemoveRestoredShortcut(itemInfo)) { removedShortcuts.add(itemInfo.id); if (DEBUG) { FileLog.w(TAG, "Removing restored shortcut promise icon" @@ -436,7 +439,7 @@ public class PackageUpdatedTask implements ModelUpdateTask { */ private boolean updateWorkspaceItemIntent(Context context, WorkspaceItemInfo si, String packageName) { - if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + if (si.itemType == ITEM_TYPE_DEEP_SHORTCUT) { // Do not update intent for deep shortcuts as they contain additional information // about the shortcut. return false; @@ -452,6 +455,15 @@ public class PackageUpdatedTask implements ModelUpdateTask { return false; } + private boolean shouldRemoveRestoredShortcut(WorkspaceItemInfo itemInfo) { + if (itemInfo.hasPromiseIconUi() && !Flags.restoreArchivedShortcuts()) { + return true; + } + return Flags.restoreArchivedShortcuts() + && !itemInfo.isArchived() + && itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT; + } + private String getOpString() { return switch (mOp) { case OP_NONE -> "NONE"; diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.kt b/src/com/android/launcher3/model/ShortcutsChangedTask.kt index 2e4f75f9dc..56e9e43d3a 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.kt +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.kt @@ -17,6 +17,7 @@ package com.android.launcher3.model import android.content.pm.ShortcutInfo import android.os.UserHandle +import com.android.launcher3.Flags import com.android.launcher3.LauncherModel.ModelUpdateTask import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT import com.android.launcher3.icons.CacheableShortcutInfo @@ -59,8 +60,11 @@ class ShortcutsChangedTask( val infoWrapper = ApplicationInfoWrapper(context, packageName, user) if (shortcuts.isEmpty()) { // Verify that the app is indeed installed. - if (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) { - // App is not installed or archived, ignoring package events + if ( + (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) || + (Flags.restoreArchivedShortcuts() && infoWrapper.isArchived()) + ) { + // App is not installed or is archived, ignoring package events return } } @@ -75,7 +79,7 @@ class ShortcutsChangedTask( val nonPinnedIds: MutableSet = HashSet(allLauncherKnownIds) val updatedWorkspaceItemInfos = ArrayList() for (fullDetails in shortcuts) { - if (!fullDetails.isPinned) { + if (!fullDetails.isPinned && !Flags.restoreArchivedShortcuts()) { continue } val shortcutId = fullDetails.id diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt index 90f11a38b0..3919eb76dd 100644 --- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt +++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt @@ -194,27 +194,36 @@ class WorkspaceItemProcessor( if (intent.`package` == null) { intent.`package` = targetPkg } + val isPreArchived = appInfoWrapper.isArchived() && c.restoreFlag != 0 + // else if cn == null => can't infer much, leave it // else if !validPkg => could be restored icon or missing sd-card when { - !TextUtils.isEmpty(targetPkg) && !validTarget -> { + !TextUtils.isEmpty(targetPkg) && (!validTarget || isPreArchived) -> { // Points to a valid app (superset of cn != null) but the apk // is not available. when { - c.restoreFlag != 0 -> { + c.restoreFlag != 0 || isPreArchived -> { // Package is not yet available but might be // installed later. - FileLog.d(TAG, "package not yet restored: $targetPkg") + FileLog.d( + TAG, + "package not yet restored: $targetPkg, itemType=${c.itemType}" + + "isPreArchived=$isPreArchived, restoreFlag=${c.restoreFlag}", + ) tempPackageKey.update(targetPkg, c.user) when { c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED) -> { // Restore has started once. } - installingPkgs.containsKey(tempPackageKey) -> { + installingPkgs.containsKey(tempPackageKey) || isPreArchived -> { // App restore has started. Update the flag c.restoreFlag = c.restoreFlag or WorkspaceItemInfo.FLAG_RESTORE_STARTED - FileLog.d(TAG, "restore started for installing app: $targetPkg") + FileLog.d( + TAG, + "restore started for installing app: $targetPkg, itemType=${c.itemType}", + ) c.updater().put(Favorites.RESTORED, c.restoreFlag).commit() } else -> { @@ -253,9 +262,18 @@ class WorkspaceItemProcessor( } } if (c.restoreFlag and WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0) { + FileLog.d( + TAG, + "restore flag set AND WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0, setting valid target to false: $targetPkg, itemType=${c.itemType}, restoreFlag=${c.restoreFlag}", + ) validTarget = false } - if (validTarget) { + if (validTarget && !isPreArchived) { + FileLog.d( + TAG, + "valid target true, marking restored: $targetPkg," + + " itemType=${c.itemType}, restoreFlag=${c.restoreFlag}", + ) // The shortcut points to a valid target (either no target // or something which is ready to be used) c.markRestored() @@ -265,7 +283,7 @@ class WorkspaceItemProcessor( when { c.restoreFlag != 0 -> { // Already verified above that user is same as default user - info = c.getRestoredItemInfo(intent) + info = c.getRestoredItemInfo(intent, isPreArchived) } c.itemType == Favorites.ITEM_TYPE_APPLICATION -> info = c.getAppShortcutInfo(intent, allowMissingTarget, useLowResIcon, false) diff --git a/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt index fb6d038744..c6863f40f1 100644 --- a/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt @@ -24,8 +24,12 @@ import android.content.pm.LauncherApps import android.content.pm.ShortcutInfo import android.os.Process.myUserHandle import android.os.UserHandle +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.launcher3.Flags import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT import com.android.launcher3.icons.BitmapInfo @@ -42,6 +46,7 @@ import com.google.common.truth.Truth.assertThat import java.util.function.Predicate 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 @@ -55,6 +60,8 @@ import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class ShortcutsChangedTaskTest { + @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() + private lateinit var shortcutsChangedTask: ShortcutsChangedTask private lateinit var modelHelper: LauncherModelHelper private lateinit var context: SandboxModelContext @@ -131,7 +138,8 @@ class ShortcutsChangedTaskTest { } @Test - fun `When installed unpinned shortcut is found then remove from workspace`() { + @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS) + fun `When installed unpinned shortcut is found with Flag off then remove from workspace`() { // Given shortcuts = listOf( @@ -162,6 +170,37 @@ class ShortcutsChangedTaskTest { ) } + @Test + @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS) + fun `When installed unpinned shortcut is found with Flag on then keep in workspace`() { + // Given + shortcuts = + listOf( + mock().apply { + whenever(isPinned).thenReturn(false) + whenever(id).thenReturn(expectedShortcutId) + } + ) + val items: IntSparseArrayMap = modelHelper.bgDataModel.itemsIdMap + items.put(expectedWai.id, expectedWai) + doReturn( + ApplicationInfo().apply { + enabled = true + flags = flags or FLAG_INSTALLED + isArchived = false + } + ) + .whenever(launcherApps) + .getApplicationInfo(eq(expectedPackage), any(), eq(user)) + doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user)) + // When + shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps) + // Then + verify(mockAppState.iconCache) + .getShortcutIcon(eq(expectedWai), any()) + verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai)) + } + @Test fun `When shortcut app is uninstalled then skip handling`() { // Given @@ -192,7 +231,8 @@ class ShortcutsChangedTaskTest { } @Test - fun `When archived pinned shortcut is found then keep in workspace`() { + @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS) + fun `When archived pinned shortcut is found with flag off then keep in workspace`() { // Given shortcuts = listOf( @@ -222,7 +262,8 @@ class ShortcutsChangedTaskTest { } @Test - fun `When archived unpinned shortcut is found then keep in workspace`() { + @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS) + fun `When archived unpinned shortcut is found with flag off then keep in workspace`() { // Given shortcuts = listOf( @@ -310,4 +351,34 @@ class ShortcutsChangedTaskTest { assertThat(modelHelper.bgDataModel.deepShortcutMap).doesNotContainKey(expectedKey) verify(mockTaskController, times(0)).bindDeepShortcuts(eq(modelHelper.bgDataModel)) } + + @Test + @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS) + fun `When restoring archived shortcut with flag on then skip handling`() { + // Given + shortcuts = + listOf( + mock().apply { + whenever(isPinned).thenReturn(true) + whenever(id).thenReturn(expectedShortcutId) + } + ) + val items: IntSparseArrayMap = modelHelper.bgDataModel.itemsIdMap + items.put(expectedWai.id, expectedWai) + doReturn( + ApplicationInfo().apply { + enabled = true + flags = flags or FLAG_INSTALLED + isArchived = true + } + ) + .whenever(launcherApps) + .getApplicationInfo(eq(expectedPackage), any(), eq(user)) + doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user)) + // When + shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps) + // Then + verify(mockTaskController, times(0)).deleteAndBindComponentsRemoved(any(), any()) + verify(mockTaskController, times(0)).bindUpdatedWorkspaceItems(any()) + } } diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt index da87dfc1ea..7a403e1826 100644 --- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt @@ -18,16 +18,19 @@ package com.android.launcher3.model import android.appwidget.AppWidgetProviderInfo import android.content.ComponentName -import android.content.Context import android.content.Intent +import android.content.pm.ApplicationInfo import android.content.pm.LauncherApps import android.content.pm.PackageInstaller import android.content.pm.ShortcutInfo import android.os.Process import android.os.UserHandle +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.util.LongSparseArray -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.Flags import com.android.launcher3.LauncherAppState import com.android.launcher3.LauncherSettings.Favorites import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP @@ -44,9 +47,14 @@ import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.LauncherAppWidgetInfo import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY import com.android.launcher3.model.data.WorkspaceItemInfo +import com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON +import com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORE_STARTED import com.android.launcher3.pm.UserCache import com.android.launcher3.shortcuts.ShortcutKey import com.android.launcher3.util.ComponentKey +import com.android.launcher3.util.ContentWriter +import com.android.launcher3.util.LauncherModelHelper +import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext import com.android.launcher3.util.PackageManagerHelper import com.android.launcher3.util.PackageUserKey import com.android.launcher3.util.UserIconInfo @@ -55,6 +63,7 @@ import com.android.launcher3.widget.WidgetInflater import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -66,6 +75,7 @@ import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -73,20 +83,23 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class WorkspaceItemProcessorTest { + @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() + @Mock private lateinit var mockIconRequestInfo: IconRequestInfo @Mock private lateinit var mockWorkspaceInfo: WorkspaceItemInfo @Mock private lateinit var mockBgDataModel: BgDataModel - @Mock private lateinit var mockContext: Context @Mock private lateinit var mockAppState: LauncherAppState @Mock private lateinit var mockPmHelper: PackageManagerHelper - @Mock private lateinit var mockLauncherApps: LauncherApps @Mock private lateinit var mockCursor: LoaderCursor @Mock private lateinit var mockUserCache: UserCache @Mock private lateinit var mockUserManagerState: UserManagerState @Mock private lateinit var mockWidgetInflater: WidgetInflater - private var intent: Intent = Intent() - private var mUserHandle: UserHandle = UserHandle(0) + lateinit var mModelHelper: LauncherModelHelper + lateinit var mContext: SandboxModelContext + lateinit var mLauncherApps: LauncherApps + private var mIntent: Intent = Intent() + private var mUserHandle: UserHandle = Process.myUserHandle() private var mIconRequestInfos: MutableList> = mutableListOf() private var mComponentName: ComponentName = ComponentName("package", "class") private var mUnlockedUsersArray: LongSparseArray = LongSparseArray() @@ -101,40 +114,35 @@ class WorkspaceItemProcessorTest { @Before fun setup() { - mUserHandle = UserHandle(0) + mModelHelper = LauncherModelHelper() + mContext = mModelHelper.sandboxContext + mLauncherApps = + mContext.spyService(LauncherApps::class.java).apply { + doReturn(true).whenever(this).isPackageEnabled("package", mUserHandle) + doReturn(true).whenever(this).isActivityEnabled(mComponentName, mUserHandle) + } + mUserHandle = Process.myUserHandle() mockIconRequestInfo = mock>() mockWorkspaceInfo = mock() mockBgDataModel = mock() mComponentName = ComponentName("package", "class") mUnlockedUsersArray = LongSparseArray(1).apply { put(101, true) } - intent = + mIntent = Intent().apply { component = mComponentName `package` = "pkg" putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "") } - mockLauncherApps = - mock().apply { - whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true) - whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true) - } - mockContext = - mock().apply { - whenever(packageManager).thenReturn(mock()) - whenever(packageManager.getUserBadgedLabel(any(), any())).thenReturn("") - whenever(applicationContext).thenReturn(ApplicationProvider.getApplicationContext()) - whenever(getSystemService(LauncherApps::class.java)).thenReturn(mockLauncherApps) - } mockAppState = mock().apply { - whenever(context).thenReturn(mockContext) + whenever(context).thenReturn(mContext) whenever(iconCache).thenReturn(mock()) whenever(iconCache.getShortcutIcon(any(), any(), any())).then {} } mockPmHelper = mock().apply { whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle)) - .thenReturn(intent) + .thenReturn(mIntent) } mockCursor = mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply { @@ -143,9 +151,9 @@ class WorkspaceItemProcessorTest { id = 1 restoreFlag = 1 serialNumber = 101 - whenever(parseIntent()).thenReturn(intent) + whenever(parseIntent()).thenReturn(mIntent) whenever(markRestored()).doAnswer { restoreFlag = 0 } - whenever(updater().put(Favorites.INTENT, intent.toUri(0)).commit()).thenReturn(1) + whenever(updater().put(Favorites.INTENT, mIntent.toUri(0)).commit()).thenReturn(1) whenever(getAppShortcutInfo(any(), any(), any(), any())) .thenReturn(mockWorkspaceInfo) whenever(createIconRequestInfo(any(), any())).thenReturn(mockIconRequestInfo) @@ -177,7 +185,7 @@ class WorkspaceItemProcessorTest { memoryLogger: LoaderMemoryLogger? = null, userCache: UserCache = mockUserCache, userManagerState: UserManagerState = mockUserManagerState, - launcherApps: LauncherApps = mockLauncherApps, + launcherApps: LauncherApps = mLauncherApps, shortcutKeyToPinnedShortcuts: Map = mKeyToPinnedShortcutsMap, app: LauncherAppState = mockAppState, bgDataModel: BgDataModel = mockBgDataModel, @@ -244,7 +252,7 @@ class WorkspaceItemProcessorTest { fun `When app has null target package then mark deleted`() { // Given - intent.apply { + mIntent.apply { component = null `package` = null } @@ -264,8 +272,8 @@ class WorkspaceItemProcessorTest { // Given mComponentName = ComponentName("", "") - intent.component = mComponentName - intent.`package` = "" + mIntent.component = mComponentName + mIntent.`package` = "" // When itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest() @@ -298,15 +306,14 @@ class WorkspaceItemProcessorTest { fun `When fallback Activity found for app then mark restored`() { // Given - mockLauncherApps = - mock().apply { - whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true) - whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false) - } + mLauncherApps.apply { + whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true) + whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false) + } mockPmHelper = mock().apply { whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle)) - .thenReturn(intent) + .thenReturn(mIntent) } // When @@ -317,7 +324,7 @@ class WorkspaceItemProcessorTest { assertWithMessage("item restoreFlag should be set to 0") .that(mockCursor.restoreFlag) .isEqualTo(0) - verify(mockCursor.updater().put(Favorites.INTENT, intent.toUri(0))).commit() + verify(mockCursor.updater().put(Favorites.INTENT, mIntent.toUri(0))).commit() assertThat(mIconRequestInfos).containsExactly(mockIconRequestInfo) verify(mockCursor).checkAndAddItem(mockWorkspaceInfo, mockBgDataModel, null) } @@ -326,11 +333,10 @@ class WorkspaceItemProcessorTest { fun `When app with disabled activity and no fallback found then mark deleted`() { // Given - mockLauncherApps = - mock().apply { - whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true) - whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false) - } + mLauncherApps.apply { + whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true) + whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false) + } mockPmHelper = mock().apply { whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle)) @@ -358,11 +364,11 @@ class WorkspaceItemProcessorTest { @Test fun `When valid Pinned Deep Shortcut then mark restored`() { - // Given mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT val expectedShortcutInfo = mock().apply { + whenever(userHandle).thenReturn(mUserHandle) whenever(id).thenReturn("") whenever(`package`).thenReturn("") whenever(activity).thenReturn(mock()) @@ -372,7 +378,7 @@ class WorkspaceItemProcessorTest { whenever(disabledReason).thenReturn(0) whenever(persons).thenReturn(EMPTY_PERSON_ARRAY) } - val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user) + val shortcutKey = ShortcutKey.fromIntent(mIntent, mockCursor.user) mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo mIconRequestInfos = mutableListOf() @@ -392,6 +398,67 @@ class WorkspaceItemProcessorTest { verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull()) } + @Test + @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS) + fun `When Archived Deep Shortcut with flag on then mark restored`() { + // Given + val mockContentWriter: ContentWriter = mock() + val mockAppInfo: ApplicationInfo = + mock().apply { + isArchived = true + enabled = true + } + val expectedRestoreFlag = FLAG_RESTORED_ICON or FLAG_RESTORE_STARTED + doReturn(mockAppInfo).whenever(mLauncherApps).getApplicationInfo(any(), any(), any()) + whenever(mockContentWriter.put(Favorites.RESTORED, expectedRestoreFlag)) + .thenReturn(mockContentWriter) + whenever(mockContentWriter.commit()).thenReturn(1) + mockCursor.apply { + itemType = ITEM_TYPE_DEEP_SHORTCUT + restoreFlag = restoreFlag or FLAG_RESTORED_ICON + whenever(updater()).thenReturn(mockContentWriter) + } + mIconRequestInfos = mutableListOf() + + // When + itemProcessorUnderTest = + createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts) + itemProcessorUnderTest.processItem() + + // Then + assertThat(mockCursor.restoreFlag and FLAG_RESTORED_ICON).isEqualTo(FLAG_RESTORED_ICON) + assertThat(mockCursor.restoreFlag and FLAG_RESTORE_STARTED).isEqualTo(FLAG_RESTORE_STARTED) + assertThat(mIconRequestInfos).isNotEmpty() + assertThat(mAllDeepShortcuts).isEmpty() + verify(mockContentWriter).put(Favorites.RESTORED, expectedRestoreFlag) + verify(mockCursor).checkAndAddItem(any(), eq(mockBgDataModel), eq(null)) + } + + @Test + @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS) + fun `When Archived Deep Shortcut with flag off then remove`() { + // Given + mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT + mIconRequestInfos = mutableListOf() + + // When + itemProcessorUnderTest = + createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts) + itemProcessorUnderTest.processItem() + + // Then + assertWithMessage("item restoreFlag should be set to 0") + .that(mockCursor.restoreFlag) + .isEqualTo(0) + assertThat(mIconRequestInfos).isEmpty() + assertThat(mAllDeepShortcuts).isEmpty() + verify(mockCursor) + .markDeleted( + "Pinned shortcut not found from request. package=pkg, user=UserHandle{0}", + "shortcut_not_found", + ) + } + @Test fun `When Pinned Deep Shortcut is not stored in ShortcutManager re-query by Shortcut ID`() { // Given @@ -406,8 +473,9 @@ class WorkspaceItemProcessorTest { whenever(disabledMessage).thenReturn("") whenever(disabledReason).thenReturn(0) whenever(persons).thenReturn(EMPTY_PERSON_ARRAY) + whenever(userHandle).thenReturn(mUserHandle) } - whenever(mockLauncherApps.getShortcuts(any(), any())).thenReturn(listOf(si)) + doReturn(listOf(si)).whenever(mLauncherApps).getShortcuts(any(), any()) mKeyToPinnedShortcutsMap.clear() mIconRequestInfos = mutableListOf() @@ -417,12 +485,12 @@ class WorkspaceItemProcessorTest { itemProcessorUnderTest.processItem() // Then - verify(mockLauncherApps).getShortcuts(any(), any()) + verify(mLauncherApps).getShortcuts(any(), any()) assertWithMessage("item restoreFlag should be set to 0") .that(mockCursor.restoreFlag) .isEqualTo(0) verify(mockCursor).markRestored() - verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull()) + verify(mockCursor).checkAndAddItem(any(), any(), eq(null)) } @Test @@ -469,11 +537,11 @@ class WorkspaceItemProcessorTest { } mIconRequestInfos = mutableListOf() // Make sure shortcuts map has expected key from expected package - intent.`package` = mComponentName.packageName - val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user) + mIntent.`package` = mComponentName.packageName + val shortcutKey = ShortcutKey.fromIntent(mIntent, mockCursor.user) mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo // set intent package back to null to test scenario - intent.`package` = null + mIntent.`package` = null // When itemProcessorUnderTest = @@ -656,7 +724,7 @@ class WorkspaceItemProcessorTest { itemProcessorUnderTest.processItem() // Then - verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull()) + verify(mockCursor).checkAndAddItem(any(), eq(mockBgDataModel), eq(null)) } @Test