Refactor ShortcutsChangedTask to Kotlin

Bug: 375414891
Test: ShortcutsChangedTaskTest
Flag: EXEMPT no functionality change
Change-Id: If80b74a80abf9bed90f1572a9fee2494d39a6829
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
deleted file mode 100644
index b5a7382..0000000
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016 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.Context;
-import android.content.pm.ShortcutInfo;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.icons.CacheableShortcutInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.ApplicationInfoWrapper;
-import com.android.launcher3.util.ItemInfoMatcher;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Handles changes due to shortcut manager updates (deep shortcut changes)
- */
-public class ShortcutsChangedTask implements ModelUpdateTask {
-
-    @NonNull
-    private final String mPackageName;
-
-    @NonNull
-    private final List<ShortcutInfo> mShortcuts;
-
-    @NonNull
-    private final UserHandle mUser;
-
-    private final boolean mUpdateIdMap;
-
-    public ShortcutsChangedTask(@NonNull final String packageName,
-            @NonNull final List<ShortcutInfo> shortcuts, @NonNull final UserHandle user,
-            final boolean updateIdMap) {
-        mPackageName = packageName;
-        mShortcuts = shortcuts;
-        mUser = user;
-        mUpdateIdMap = updateIdMap;
-    }
-
-    @Override
-    public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
-            @NonNull AllAppsList apps) {
-        final LauncherAppState app = taskController.getApp();
-        final Context context = app.getContext();
-        // Find WorkspaceItemInfo's that have changed on the workspace.
-        ArrayList<WorkspaceItemInfo> matchingWorkspaceItems = new ArrayList<>();
-
-        synchronized (dataModel) {
-            dataModel.forAllWorkspaceItemInfos(mUser, si -> {
-                if ((si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
-                        && mPackageName.equals(si.getIntent().getPackage())) {
-                    matchingWorkspaceItems.add(si);
-                }
-            });
-        }
-
-        if (!matchingWorkspaceItems.isEmpty()) {
-            ApplicationInfoWrapper infoWrapper =
-                    new ApplicationInfoWrapper(context, mPackageName, mUser);
-            if (mShortcuts.isEmpty()) {
-                // Verify that the app is indeed installed.
-                if (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) {
-                    // App is not installed or archived, ignoring package events
-                    return;
-                }
-            }
-            // Update the workspace to reflect the changes to updated shortcuts residing on it.
-            List<String> allLauncherKnownIds = matchingWorkspaceItems.stream()
-                    .map(WorkspaceItemInfo::getDeepShortcutId)
-                    .distinct()
-                    .collect(Collectors.toList());
-            List<ShortcutInfo> shortcuts = new ShortcutRequest(context, mUser)
-                    .forPackage(mPackageName, allLauncherKnownIds)
-                    .query(ShortcutRequest.ALL);
-
-            Set<String> nonPinnedIds = new HashSet<>(allLauncherKnownIds);
-            ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
-            for (ShortcutInfo fullDetails : shortcuts) {
-                if (!fullDetails.isPinned()) {
-                    continue;
-                }
-                String sid = fullDetails.getId();
-                nonPinnedIds.remove(sid);
-                matchingWorkspaceItems
-                        .stream()
-                        .filter(itemInfo -> sid.equals(itemInfo.getDeepShortcutId()))
-                        .forEach(workspaceItemInfo -> {
-                            workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
-                            app.getIconCache().getShortcutIcon(workspaceItemInfo,
-                                    new CacheableShortcutInfo(fullDetails, infoWrapper));
-                            updatedWorkspaceItemInfos.add(workspaceItemInfo);
-                        });
-            }
-
-            taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
-            if (!nonPinnedIds.isEmpty()) {
-                taskController.deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
-                        nonPinnedIds.stream()
-                                .map(id -> new ShortcutKey(mPackageName, mUser, id))
-                                .collect(Collectors.toSet())),
-                        "removed because the shortcut is no longer available in shortcut service");
-            }
-        }
-
-        if (mUpdateIdMap) {
-            // Update the deep shortcut map if the list of ids has changed for an activity.
-            dataModel.updateDeepShortcutCounts(mPackageName, mUser, mShortcuts);
-            taskController.bindDeepShortcuts(dataModel);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.kt b/src/com/android/launcher3/model/ShortcutsChangedTask.kt
new file mode 100644
index 0000000..2e4f75f
--- /dev/null
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2025 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.pm.ShortcutInfo
+import android.os.UserHandle
+import com.android.launcher3.LauncherModel.ModelUpdateTask
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+import com.android.launcher3.icons.CacheableShortcutInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.shortcuts.ShortcutRequest
+import com.android.launcher3.util.ApplicationInfoWrapper
+import com.android.launcher3.util.ItemInfoMatcher
+
+/** Handles changes due to shortcut manager updates (deep shortcut changes) */
+class ShortcutsChangedTask(
+    private val packageName: String,
+    private val shortcuts: List<ShortcutInfo>,
+    private val user: UserHandle,
+    private val shouldUpdateIdMap: Boolean,
+) : ModelUpdateTask {
+
+    override fun execute(
+        taskController: ModelTaskController,
+        dataModel: BgDataModel,
+        apps: AllAppsList,
+    ) {
+        val app = taskController.app
+        val context = app.context
+        // Find WorkspaceItemInfo's that have changed on the workspace.
+        val matchingWorkspaceItems = ArrayList<WorkspaceItemInfo>()
+
+        synchronized(dataModel) {
+            dataModel.forAllWorkspaceItemInfos(user) { wai: WorkspaceItemInfo ->
+                if (
+                    (wai.itemType == ITEM_TYPE_DEEP_SHORTCUT) &&
+                        packageName == wai.getIntent().getPackage()
+                ) {
+                    matchingWorkspaceItems.add(wai)
+                }
+            }
+        }
+
+        if (matchingWorkspaceItems.isNotEmpty()) {
+            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
+                    return
+                }
+            }
+            // Update the workspace to reflect the changes to updated shortcuts residing on it.
+            val allLauncherKnownIds =
+                matchingWorkspaceItems.map { item -> item.deepShortcutId }.distinct()
+            val shortcuts: List<ShortcutInfo> =
+                ShortcutRequest(context, user)
+                    .forPackage(packageName, allLauncherKnownIds)
+                    .query(ShortcutRequest.ALL)
+
+            val nonPinnedIds: MutableSet<String> = HashSet(allLauncherKnownIds)
+            val updatedWorkspaceItemInfos = ArrayList<WorkspaceItemInfo>()
+            for (fullDetails in shortcuts) {
+                if (!fullDetails.isPinned) {
+                    continue
+                }
+                val shortcutId = fullDetails.id
+                nonPinnedIds.remove(shortcutId)
+                matchingWorkspaceItems
+                    .filter { itemInfo: WorkspaceItemInfo -> shortcutId == itemInfo.deepShortcutId }
+                    .forEach { workspaceItemInfo: WorkspaceItemInfo ->
+                        workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context)
+                        app.iconCache.getShortcutIcon(
+                            workspaceItemInfo,
+                            CacheableShortcutInfo(fullDetails, infoWrapper),
+                        )
+                        updatedWorkspaceItemInfos.add(workspaceItemInfo)
+                    }
+            }
+
+            taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos)
+            if (nonPinnedIds.isNotEmpty()) {
+                taskController.deleteAndBindComponentsRemoved(
+                    ItemInfoMatcher.ofShortcutKeys(
+                        nonPinnedIds
+                            .map { id: String? -> ShortcutKey(packageName, user, id) }
+                            .toSet()
+                    ),
+                    "removed because the shortcut is no longer available in shortcut service",
+                )
+            }
+        }
+
+        if (shouldUpdateIdMap) {
+            // Update the deep shortcut map if the list of ids has changed for an activity.
+            dataModel.updateDeepShortcutCounts(packageName, user, shortcuts)
+            taskController.bindDeepShortcuts(dataModel)
+        }
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
new file mode 100644
index 0000000..fb6d038
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2025 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.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.ApplicationInfo.FLAG_INSTALLED
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.os.Process.myUserHandle
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.CacheableShortcutInfo
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.IntSparseArrayMap
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Predicate
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutsChangedTaskTest {
+    private lateinit var shortcutsChangedTask: ShortcutsChangedTask
+    private lateinit var modelHelper: LauncherModelHelper
+    private lateinit var context: SandboxModelContext
+    private lateinit var launcherApps: LauncherApps
+    private var shortcuts: List<ShortcutInfo> = emptyList()
+
+    private val expectedPackage: String = "expected"
+    private val expectedShortcutId: String = "shortcut_id"
+    private val user: UserHandle = myUserHandle()
+    private val mockTaskController: ModelTaskController = mock()
+    private val mockAllApps: AllAppsList = mock()
+    private val mockAppState: LauncherAppState = mock()
+    private val mockIconCache: IconCache = mock()
+
+    private val expectedWai =
+        WorkspaceItemInfo().apply {
+            id = 1
+            itemType = ITEM_TYPE_DEEP_SHORTCUT
+            intent =
+                Intent().apply {
+                    `package` = expectedPackage
+                    putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, expectedShortcutId)
+                }
+        }
+
+    @Before
+    fun setup() {
+        modelHelper = LauncherModelHelper()
+        modelHelper.loadModelSync()
+        context = modelHelper.sandboxContext
+        launcherApps = context.spyService(LauncherApps::class.java)
+        whenever(mockTaskController.app).thenReturn(mockAppState)
+        whenever(mockAppState.context).thenReturn(context)
+        whenever(mockAppState.iconCache).thenReturn(mockIconCache)
+        whenever(mockIconCache.getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>()))
+            .then { _ -> { expectedWai.bitmap = BitmapInfo.LOW_RES_INFO } }
+        shortcuts = emptyList()
+        shortcutsChangedTask = ShortcutsChangedTask(expectedPackage, shortcuts, user, false)
+    }
+
+    @After
+    fun teardown() {
+        modelHelper.destroy()
+    }
+
+    @Test
+    fun `When installed pinned shortcut is found then keep in workspace`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(true)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = 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<CacheableShortcutInfo>())
+        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+    }
+
+    @Test
+    fun `When installed unpinned shortcut is found then remove from workspace`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(false)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = 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(mockTaskController)
+            .deleteAndBindComponentsRemoved(
+                any<Predicate<ItemInfo?>>(),
+                eq("removed because the shortcut is no longer available in shortcut service"),
+            )
+    }
+
+    @Test
+    fun `When shortcut app is uninstalled then skip handling`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(true)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+        items.put(expectedWai.id, expectedWai)
+        doReturn(
+                ApplicationInfo().apply {
+                    enabled = true
+                    flags = flags and FLAG_INSTALLED.inv()
+                    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(mockTaskController, times(0)).deleteAndBindComponentsRemoved(any(), any())
+        verify(mockTaskController, times(0)).bindUpdatedWorkspaceItems(any())
+    }
+
+    @Test
+    fun `When archived pinned shortcut is found then keep in workspace`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(true)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = 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(mockAppState.iconCache)
+            .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+    }
+
+    @Test
+    fun `When archived unpinned shortcut is found then keep in workspace`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(true)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = 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(mockAppState.iconCache)
+            .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+    }
+
+    @Test
+    fun `When updateIdMap true then trigger deep shortcut binding`() {
+        // Given
+        val expectedShortcut =
+            mock<ShortcutInfo>().apply {
+                whenever(isEnabled).thenReturn(true)
+                whenever(isDeclaredInManifest).thenReturn(true)
+                whenever(activity).thenReturn(ComponentName(expectedPackage, "expectedClass"))
+                whenever(id).thenReturn(expectedShortcutId)
+                whenever(userHandle).thenReturn(user)
+            }
+        shortcuts = listOf(expectedShortcut)
+        val expectedKey = ComponentKey(expectedShortcut.activity, expectedShortcut.userHandle)
+        doReturn(ApplicationInfo())
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        shortcutsChangedTask =
+            ShortcutsChangedTask(
+                packageName = expectedPackage,
+                shortcuts = shortcuts,
+                user = user,
+                shouldUpdateIdMap = true,
+            )
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        assertThat(modelHelper.bgDataModel.deepShortcutMap).containsEntry(expectedKey, 1)
+        verify(mockTaskController).bindDeepShortcuts(eq(modelHelper.bgDataModel))
+    }
+
+    @Test
+    fun `When updateIdMap false then do not trigger deep shortcut binding`() {
+        // Given
+        val expectedShortcut =
+            mock<ShortcutInfo>().apply {
+                whenever(isEnabled).thenReturn(true)
+                whenever(isDeclaredInManifest).thenReturn(true)
+                whenever(activity).thenReturn(ComponentName(expectedPackage, "expectedClass"))
+                whenever(id).thenReturn(expectedShortcutId)
+                whenever(userHandle).thenReturn(user)
+            }
+        shortcuts = listOf(expectedShortcut)
+        val expectedKey = ComponentKey(expectedShortcut.activity, expectedShortcut.userHandle)
+        doReturn(ApplicationInfo())
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        shortcutsChangedTask =
+            ShortcutsChangedTask(
+                packageName = expectedPackage,
+                shortcuts = shortcuts,
+                user = user,
+                shouldUpdateIdMap = false,
+            )
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        assertThat(modelHelper.bgDataModel.deepShortcutMap).doesNotContainKey(expectedKey)
+        verify(mockTaskController, times(0)).bindDeepShortcuts(eq(modelHelper.bgDataModel))
+    }
+}