Get person avatar icon on a bg thread

We're making a binder call to launcher, and it shouldn't block the main
thread.

Tracking this change and the other changes in the child bugs of b/315143361
under the same generic flag.

Fix: 320274812
Bug: 315143361
Test: posted a few high prio conversation notifs and checked that they
look good on aod, the shelf and status bar
Flag: ACONFIG com.android.systemui.notifications_background_icons DEVELOPMENT

Change-Id: Ie03cf3698c2d46c75012792a5628a5b1068b244b
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 8c8975f..56e8c677 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -105,10 +105,10 @@
 }
 
 flag {
-    name: "notifications_background_media_icons"
+    name: "notifications_background_icons"
     namespace: "systemui"
-    description: "Updates icons for media notifications in the background."
-    bug: "315143160"
+    description: "Moves part of the notification icon updates to the background."
+    bug: "315143361"
     metadata {
         purpose: PURPOSE_BUGFIX
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a4011fd..1003050 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -69,6 +69,7 @@
 import com.android.systemui.toast.ToastModule;
 import com.android.systemui.unfold.SysUIUnfoldStartableModule;
 import com.android.systemui.unfold.UnfoldTransitionModule;
+import com.android.systemui.util.kotlin.SysUICoroutinesModule;
 import com.android.systemui.volume.dagger.VolumeModule;
 import com.android.systemui.wallpapers.dagger.WallpaperModule;
 
@@ -117,6 +118,7 @@
         ShadeModule.class,
         StartCentralSurfacesModule.class,
         SceneContainerFrameworkModule.class,
+        SysUICoroutinesModule.class,
         SysUIUnfoldStartableModule.class,
         UnfoldTransitionModule.Startables.class,
         ToastModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 072f56d..dcfccd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -61,7 +61,7 @@
                 return false;
             }
 
-            if (!Flags.notificationsBackgroundMediaIcons()) {
+            if (!Flags.notificationsBackgroundIcons()) {
                 inflateOrUpdateIcons(entry);
             }
 
@@ -73,14 +73,14 @@
         @Override
         public void onEntryInit(@NonNull NotificationEntry entry) {
             // We default to STATE_ICONS_UNINFLATED anyway, so there's no need to initialize it.
-            if (!Flags.notificationsBackgroundMediaIcons()) {
+            if (!Flags.notificationsBackgroundIcons()) {
                 mIconsState.put(entry, STATE_ICONS_UNINFLATED);
             }
         }
 
         @Override
         public void onEntryAdded(@NonNull NotificationEntry entry) {
-            if (Flags.notificationsBackgroundMediaIcons()) {
+            if (Flags.notificationsBackgroundIcons()) {
                 if (isMediaNotification(entry.getSbn())) {
                     inflateOrUpdateIcons(entry);
                 }
@@ -94,7 +94,7 @@
                 mIconsState.put(entry, STATE_ICONS_UNINFLATED);
             }
 
-            if (Flags.notificationsBackgroundMediaIcons()) {
+            if (Flags.notificationsBackgroundIcons()) {
                 if (isMediaNotification(entry.getSbn())) {
                     inflateOrUpdateIcons(entry);
                 }
@@ -120,7 +120,7 @@
                 break;
             case STATE_ICONS_INFLATED:
                 try {
-                    mIconManager.updateIcons(entry);
+                    mIconManager.updateIcons(entry, /* usingCache = */ false);
                 } catch (InflationException e) {
                     reportInflationError(entry, e);
                     mIconsState.put(entry, STATE_ICONS_ERROR);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index c5b55c7..332b49c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -138,7 +138,7 @@
 
         if (entry.rowExists()) {
             mLogger.logUpdatingRow(entry, params);
-            mIconManager.updateIcons(entry);
+            mIconManager.updateIcons(entry, /* usingCache = */ false);
             ExpandableNotificationRow row = entry.getRow();
             row.reset();
             updateRow(entry, row);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index a5f42bb..a900e45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -28,14 +28,24 @@
 import android.widget.ImageView
 import com.android.app.tracing.traceSection
 import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.InflationException
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import java.util.concurrent.ConcurrentHashMap
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Inflates and updates icons associated with notifications
@@ -53,9 +63,18 @@
 constructor(
     private val notifCollection: CommonNotifCollection,
     private val launcherApps: LauncherApps,
-    private val iconBuilder: IconBuilder
+    private val iconBuilder: IconBuilder,
+    @Application private val applicationCoroutineScope: CoroutineScope,
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Main private val mainCoroutineContext: CoroutineContext,
 ) : ConversationIconManager {
     private var unimportantConversationKeys: Set<String> = emptySet()
+    /**
+     * A map of running jobs for fetching the person avatar from launcher. The key is the
+     * notification entry key.
+     */
+    private var launcherPeopleAvatarIconJobs: ConcurrentHashMap<String, Job> =
+        ConcurrentHashMap<String, Job>()
 
     fun attach() {
         notifCollection.addCollectionListener(entryListener)
@@ -136,13 +155,23 @@
      * @throws InflationException Exception if required icons are not valid or specified
      */
     @Throws(InflationException::class)
-    fun updateIcons(entry: NotificationEntry) =
+    fun updateIcons(entry: NotificationEntry, usingCache: Boolean = false) =
         traceSection("IconManager.updateIcons") {
             if (!entry.icons.areIconsAvailable) {
                 return@traceSection
             }
-            entry.icons.smallIconDescriptor = null
-            entry.icons.peopleAvatarDescriptor = null
+
+            if (usingCache && !Flags.notificationsBackgroundIcons()) {
+                Log.wtf(
+                    TAG,
+                    "Updating using the cache is not supported when the " +
+                        "notifications_background_conversation_icons flag is off"
+                )
+            }
+            if (!usingCache || !Flags.notificationsBackgroundIcons()) {
+                entry.icons.smallIconDescriptor = null
+                entry.icons.peopleAvatarDescriptor = null
+            }
 
             val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
             val notificationContentDescription =
@@ -188,7 +217,7 @@
     @Throws(InflationException::class)
     private fun getIconDescriptor(entry: NotificationEntry, redact: Boolean): StatusBarIcon {
         val n = entry.sbn.notification
-        val showPeopleAvatar = isImportantConversation(entry) && !redact
+        val showPeopleAvatar = !redact && isImportantConversation(entry)
 
         val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
         val smallIconDescriptor = entry.icons.smallIconDescriptor
@@ -208,26 +237,18 @@
             })
                 ?: throw InflationException("No icon in notification from " + entry.sbn.packageName)
 
-        val ic =
-            StatusBarIcon(
-                entry.sbn.user,
-                entry.sbn.packageName,
-                icon,
-                n.iconLevel,
-                n.number,
-                iconBuilder.getIconContentDescription(n)
-            )
+        val sbi = icon.toStatusBarIcon(entry)
 
         // Cache if important conversation.
         if (isImportantConversation(entry)) {
             if (showPeopleAvatar) {
-                entry.icons.peopleAvatarDescriptor = ic
+                entry.icons.peopleAvatarDescriptor = sbi
             } else {
-                entry.icons.smallIconDescriptor = ic
+                entry.icons.smallIconDescriptor = sbi
             }
         }
 
-        return ic
+        return sbi
     }
 
     @Throws(InflationException::class)
@@ -243,16 +264,69 @@
         }
     }
 
-    @Throws(InflationException::class)
-    private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
-        var ic: Icon? = null
+    private fun Icon.toStatusBarIcon(entry: NotificationEntry): StatusBarIcon {
+        val n = entry.sbn.notification
+        return StatusBarIcon(
+            entry.sbn.user,
+            entry.sbn.packageName,
+            /* icon = */ this,
+            n.iconLevel,
+            n.number,
+            iconBuilder.getIconContentDescription(n)
+        )
+    }
 
-        val shortcut = entry.ranking.conversationShortcutInfo
-        if (shortcut != null) {
-            ic = launcherApps.getShortcutIcon(shortcut)
+    private suspend fun getLauncherShortcutIconForPeopleAvatar(entry: NotificationEntry) =
+        withContext(bgCoroutineContext) {
+            var icon: Icon? = null
+            val shortcut = entry.ranking.conversationShortcutInfo
+            if (shortcut != null) {
+                try {
+                    icon = launcherApps.getShortcutIcon(shortcut)
+                } catch (e: Exception) {
+                    Log.e(
+                        TAG,
+                        "Error calling LauncherApps#getShortcutIcon for notification $entry: $e"
+                    )
+                }
+            }
+
+            // Once we have the icon, updating it should happen on the main thread.
+            if (icon != null) {
+                withContext(mainCoroutineContext) {
+                    val iconDescriptor = icon.toStatusBarIcon(entry)
+
+                    // Cache the value
+                    entry.icons.peopleAvatarDescriptor = iconDescriptor
+
+                    // Update the icons using the cached value
+                    updateIcons(entry = entry, usingCache = true)
+                }
+            }
         }
 
-        // Fall back to extract from message
+    @Throws(InflationException::class)
+    private fun createPeopleAvatar(entry: NotificationEntry): Icon {
+        var ic: Icon? = null
+
+        if (Flags.notificationsBackgroundIcons()) {
+            // Ideally we want to get the icon from launcher, but this is a binder transaction that
+            // may take longer so let's kick it off on a background thread and use a placeholder in
+            // the meantime.
+            // Cancel the previous job if necessary.
+            launcherPeopleAvatarIconJobs[entry.key]?.cancel()
+            launcherPeopleAvatarIconJobs[entry.key] =
+                applicationCoroutineScope
+                    .launch { getLauncherShortcutIconForPeopleAvatar(entry) }
+                    .apply { invokeOnCompletion { launcherPeopleAvatarIconJobs.remove(entry.key) } }
+        } else {
+            val shortcut = entry.ranking.conversationShortcutInfo
+            if (shortcut != null) {
+                ic = launcherApps.getShortcutIcon(shortcut)
+            }
+        }
+
+        // Try to extract from message
         if (ic == null) {
             val extras: Bundle = entry.sbn.notification.extras
             val messages =
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index cabe831..d10554f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -32,7 +32,7 @@
 
 private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true
 
-/** Providers for various SystemIU specific coroutines-related constructs. */
+/** Providers for various SystemUI-specific coroutines-related constructs. */
 @Module
 class SysUICoroutinesModule {
     @Provides
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
index b548117..e90a3ac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
@@ -157,52 +158,52 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+    @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
     public void inflateMediaNotificationIconsMediaEnabled_old() throws InflationException {
         finishSetupWithMediaFeatureFlagEnabled(true);
 
         mListener.onEntryInit(mMediaEntry);
         mListener.onEntryAdded(mMediaEntry);
         verify(mIconManager, never()).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry),  /* usingCache = */ eq(false));
 
         mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
         mListener.onEntryCleanUp(mMediaEntry);
         mListener.onEntryInit(mMediaEntry);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry),  /* usingCache = */ eq(false));
 
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(2)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+    @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
     public void inflateMediaNotificationIconsMediaEnabled_new() throws InflationException {
         finishSetupWithMediaFeatureFlagEnabled(true);
 
         mListener.onEntryInit(mMediaEntry);
         mListener.onEntryAdded(mMediaEntry);
         verify(mIconManager).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
         clearInvocations(mIconManager);
 
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, never()).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         mListener.onEntryUpdated(mMediaEntry);
         verify(mIconManager, never()).createIcons(eq(mMediaEntry));
-        verify(mIconManager).updateIcons(eq(mMediaEntry));
+        verify(mIconManager).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
 
         mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
         mListener.onEntryCleanUp(mMediaEntry);
@@ -211,40 +212,40 @@
         mListener.onEntryInit(mMediaEntry);
         mListener.onEntryAdded(mMediaEntry);
         verify(mIconManager).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+    @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
     public void inflationException_old() throws InflationException {
         finishSetupWithMediaFeatureFlagEnabled(true);
 
         mListener.onEntryInit(mMediaEntry);
         mListener.onEntryAdded(mMediaEntry);
         verify(mIconManager, never()).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         doThrow(InflationException.class).when(mIconManager).createIcons(eq(mMediaEntry));
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry),  /* usingCache = */ eq(false));
 
         mListener.onEntryUpdated(mMediaEntry);
         verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
 
         doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
         mFilter.shouldFilterOut(mMediaEntry, 0);
         verify(mIconManager, times(2)).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+    @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
     public void inflationException_new() throws InflationException {
         finishSetupWithMediaFeatureFlagEnabled(true);
 
@@ -253,19 +254,19 @@
         mListener.onEntryInit(mMediaEntry);
         mListener.onEntryAdded(mMediaEntry);
         verify(mIconManager).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
         clearInvocations(mIconManager);
 
         mListener.onEntryUpdated(mMediaEntry);
         verify(mIconManager).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
         clearInvocations(mIconManager);
 
         doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
 
         mListener.onEntryUpdated(mMediaEntry);
         verify(mIconManager).createIcons(eq(mMediaEntry));
-        verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+        verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
     }
 
     private void finishSetupWithMediaFeatureFlagEnabled(boolean mediaFeatureFlagEnabled) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index a12806b..4ac9dc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.icon
 
 import android.app.ActivityManager
@@ -38,6 +40,10 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,6 +75,11 @@
     @Mock private lateinit var notifCollection: CommonNotifCollection
     @Mock private lateinit var launcherApps: LauncherApps
 
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val mainContext = testScope.coroutineContext
+    private val bgContext = testScope.backgroundScope.coroutineContext
+
     private val iconBuilder = IconBuilder(context)
 
     private lateinit var iconManager: IconManager
@@ -85,7 +96,15 @@
         `when`(shortcut.icon).thenReturn(shortcutIc)
         `when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc)
 
-        iconManager = IconManager(notifCollection, launcherApps, iconBuilder)
+        iconManager =
+            IconManager(
+                notifCollection,
+                launcherApps,
+                iconBuilder,
+                testScope,
+                bgContext,
+                mainContext,
+            )
     }
 
     @Test
@@ -94,6 +113,7 @@
             notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
     }
 
@@ -103,6 +123,7 @@
             notificationEntry(hasShortcut = false, hasMessageSenderIcon = true, hasLargeIcon = true)
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(messageIc)
     }
 
@@ -116,6 +137,7 @@
             )
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(largeIc)
     }
 
@@ -129,6 +151,7 @@
             )
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
     }
 
@@ -143,6 +166,7 @@
             )
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
     }
 
@@ -161,6 +185,7 @@
         entry?.setSensitive(true, true)
         entry?.channel?.isImportantConversation = true
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
         assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
         assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
@@ -175,6 +200,7 @@
         entry?.let { iconManager.createIcons(it) }
         // Updating the icons after creation shouldn't break anything
         entry?.let { iconManager.updateIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
         assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
         assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
@@ -187,9 +213,11 @@
         entry?.channel?.isImportantConversation = true
         entry?.setSensitive(true, true)
         entry?.let { iconManager.createIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
         entry?.setSensitive(false, false)
         entry?.let { iconManager.updateIcons(it) }
+        testScope.runCurrent()
         assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(shortcutIc)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index fb49499f..2786733 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -53,6 +53,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -102,6 +103,9 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.test.TestScope;
+
 /**
  * A helper class to create {@link ExpandableNotificationRow} (for both individual and group
  * notifications).
@@ -140,6 +144,10 @@
     private final FakeFeatureFlags mFeatureFlags;
     private final SystemClock mSystemClock;
     private final RowInflaterTaskLogger mRowInflaterTaskLogger;
+    private final TestScope mTestScope = TestScopeProvider.getTestScope();
+    private final CoroutineContext mBgCoroutineContext =
+            mTestScope.getBackgroundScope().getCoroutineContext();
+    private final CoroutineContext mMainCoroutineContext = mTestScope.getCoroutineContext();
 
     public NotificationTestHelper(
             Context context,
@@ -169,7 +177,10 @@
         mIconManager = new IconManager(
                 mock(CommonNotifCollection.class),
                 mock(LauncherApps.class),
-                new IconBuilder(mContext));
+                new IconBuilder(mContext),
+                mTestScope,
+                mBgCoroutineContext,
+                mMainCoroutineContext);
 
         NotificationContentInflater contentBinder = new NotificationContentInflater(
                 mock(NotifRemoteViewCache.class),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index 0b13858..b34681a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -2,6 +2,7 @@
 
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 
@@ -9,3 +10,7 @@
 var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
 var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
 var Kosmos.testCase: SysuiTestCase by Fixture()
+var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
+    testScope.backgroundScope.coroutineContext
+}
+var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
index d3a8e0c..0950f04 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
@@ -18,7 +18,19 @@
 
 import android.content.pm.launcherApps
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.mainCoroutineContext
 import com.android.systemui.statusbar.notification.collection.notifcollection.commonNotifCollection
 
 val Kosmos.iconManager by
-    Kosmos.Fixture { IconManager(commonNotifCollection, launcherApps, iconBuilder) }
+    Kosmos.Fixture {
+        IconManager(
+            commonNotifCollection,
+            launcherApps,
+            iconBuilder,
+            applicationCoroutineScope,
+            backgroundCoroutineContext,
+            mainCoroutineContext,
+        )
+    }