Loading packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -53,7 +53,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { private val shadeRootview = mock<WindowRootView>() private val positionRepository = FakeShadeDisplayRepository() private val shadeContext = mock<Context>() private val contextStore = FakeDisplayWindowPropertiesRepository() private val contextStore = FakeDisplayWindowPropertiesRepository(context) private val testScope = TestScope(UnconfinedTestDispatcher()) private val shadeWm = mock<WindowManager>() private val resources = mock<Resources>() Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStoreTest.kt 0 → 100644 +149 −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.systemui.statusbar.notification.icon.ui.viewbinder import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.display.domain.interactor.displayWindowPropertiesInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifCollection import com.android.systemui.statusbar.notification.collection.notifPipeline import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.icon.iconManager import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) class ConnectedDisplaysStatusBarNotificationIconViewStoreTest : SysuiTestCase() { private val kosmos = testKosmos() private val underTest = ConnectedDisplaysStatusBarNotificationIconViewStore( TEST_DISPLAY_ID, kosmos.notifCollection, kosmos.iconManager, kosmos.displayWindowPropertiesInteractor, kosmos.notifPipeline, ) private val notifCollectionListeners = mutableListOf<NotifCollectionListener>() @Before fun setupNoticCollectionListener() { whenever(kosmos.notifPipeline.addCollectionListener(any())).thenAnswer { invocation -> notifCollectionListeners.add(invocation.arguments[0] as NotifCollectionListener) } } @Before fun activate() { underTest.activateIn(kosmos.testScope) } @Test fun iconView_unknownKey_returnsNull() = kosmos.testScope.runTest { val unknownKey = "unknown key" assertThat(underTest.iconView(unknownKey)).isNull() } @Test fun iconView_knownKey_returnsNonNull() = kosmos.testScope.runTest { val entry = createEntry() whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry) assertThat(underTest.iconView(entry.key)).isNotNull() } @Test fun iconView_knownKey_calledMultipleTimes_returnsSameInstance() = kosmos.testScope.runTest { val entry = createEntry() whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry) val first = underTest.iconView(entry.key) val second = underTest.iconView(entry.key) assertThat(first).isSameInstanceAs(second) } @Test fun iconView_knownKey_afterNotificationRemoved_returnsNewInstance() = kosmos.testScope.runTest { val entry = createEntry() whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry) val first = underTest.iconView(entry.key) notifCollectionListeners.forEach { it.onEntryRemoved(entry, /* reason= */ 0) } val second = underTest.iconView(entry.key) assertThat(first).isNotSameInstanceAs(second) } private fun createEntry(): NotificationEntry { val channelId = "channelId" val notificationChannel = NotificationChannel(channelId, "name", NotificationManager.IMPORTANCE_DEFAULT) val notification = Notification.Builder(context, channelId) .setContentTitle("Title") .setContentText("Content text") .setSmallIcon(com.android.systemui.res.R.drawable.icon) .build() val statusBarNotification = SbnBuilder().setNotification(notification).build() val ranking = RankingBuilder() .setChannel(notificationChannel) .setKey(statusBarNotification.key) .build() return NotificationEntry( /* sbn = */ statusBarNotification, /* ranking = */ ranking, /* creationTime = */ 1234L, ) } private companion object { const val TEST_DISPLAY_ID = 1234 } } packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt +5 −1 Original line number Diff line number Diff line Loading @@ -25,7 +25,11 @@ import javax.inject.Inject /** Testable wrapper around Context. */ class IconBuilder @Inject constructor(private val context: Context) { fun createIconView(entry: NotificationEntry): StatusBarIconView { @JvmOverloads fun createIconView( entry: NotificationEntry, context: Context = this.context, ): StatusBarIconView { return StatusBarIconView( context, "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}", Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +54 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.icon import android.app.Notification import android.app.Notification.MessagingStyle import android.app.Person import android.content.Context import android.content.pm.LauncherApps import android.graphics.drawable.Icon import android.os.Build Loading @@ -36,6 +37,7 @@ 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.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.InflationException import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection Loading Loading @@ -68,6 +70,17 @@ constructor( @Background private val bgCoroutineContext: CoroutineContext, @Main private val mainCoroutineContext: CoroutineContext, ) : ConversationIconManager { /** * A listener that is notified when a [NotificationEntry] has been updated and the associated * icons have to be updated as well. */ fun interface OnIconUpdateRequiredListener { fun onIconUpdateRequired(entry: NotificationEntry) } private val onIconUpdateRequiredListeners = mutableSetOf<OnIconUpdateRequiredListener>() private var unimportantConversationKeys: Set<String> = emptySet() /** * A map of running jobs for fetching the person avatar from launcher. The key is the Loading @@ -76,6 +89,16 @@ constructor( private var launcherPeopleAvatarIconJobs: ConcurrentHashMap<String, Job> = ConcurrentHashMap<String, Job>() fun addIconsUpdateListener(listener: OnIconUpdateRequiredListener) { StatusBarConnectedDisplays.assertInNewMode() onIconUpdateRequiredListeners += listener } fun removeIconsUpdateListener(listener: OnIconUpdateRequiredListener) { StatusBarConnectedDisplays.assertInNewMode() onIconUpdateRequiredListeners -= listener } fun attach() { notifCollection.addCollectionListener(entryListener) } Loading Loading @@ -111,6 +134,21 @@ constructor( } } /** * Inflate the [StatusBarIconView] for the given [NotificationEntry], using the specified * [Context]. */ fun createSbIconView(context: Context, entry: NotificationEntry): StatusBarIconView = traceSection("IconManager.createSbIconView") { StatusBarConnectedDisplays.assertInNewMode() val sbIcon = iconBuilder.createIconView(entry, context) sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE val (normalIconDescriptor, _) = getIconDescriptors(entry) setIcon(entry, normalIconDescriptor, sbIcon) return sbIcon } /** * Inflate icon views for each icon variant and assign appropriate icons to them. Stores the * result in [NotificationEntry.getIcons]. Loading Loading @@ -159,6 +197,18 @@ constructor( } } /** Update the [StatusBarIconView] for the given [NotificationEntry]. */ fun updateSbIcon(entry: NotificationEntry, iconView: StatusBarIconView) = traceSection("IconManager.updateSbIcon") { StatusBarConnectedDisplays.assertInNewMode() val (normalIconDescriptor, _) = getIconDescriptors(entry) val notificationContentDescription = entry.sbn.notification?.let { iconBuilder.getIconContentDescription(it) } iconView.setNotification(entry.sbn, notificationContentDescription) setIcon(entry, normalIconDescriptor, iconView) } /** * Update the notification icons. * Loading @@ -172,6 +222,10 @@ constructor( return@traceSection } if (StatusBarConnectedDisplays.isEnabled) { onIconUpdateRequiredListeners.onEach { it.onIconUpdateRequired(entry) } } if (usingCache && !Flags.notificationsBackgroundIcons()) { Log.wtf( TAG, Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt 0 → 100644 +94 −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.systemui.statusbar.notification.icon.ui.viewbinder import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractor import com.android.systemui.lifecycle.Activatable import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.icon.IconManager import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope /** [IconViewStore] for the status bar on multiple displays. */ class ConnectedDisplaysStatusBarNotificationIconViewStore @AssistedInject constructor( @Assisted private val displayId: Int, private val notifCollection: NotifCollection, private val iconManager: IconManager, private val displayWindowPropertiesInteractor: DisplayWindowPropertiesInteractor, private val notifPipeline: NotifPipeline, ) : IconViewStore, Activatable { private val cachedIcons = ConcurrentHashMap<String, StatusBarIconView>() private val iconUpdateRequiredListener = object : IconManager.OnIconUpdateRequiredListener { override fun onIconUpdateRequired(entry: NotificationEntry) { val iconView = iconView(entry.key) ?: return iconManager.updateSbIcon(entry, iconView) } } private val notifCollectionListener = object : NotifCollectionListener { override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { cachedIcons.remove(entry.key) } } override fun iconView(key: String): StatusBarIconView? { val entry = notifCollection.getEntry(key) ?: return null return cachedIcons.computeIfAbsent(key) { val context = displayWindowPropertiesInteractor.getForStatusBar(displayId).context iconManager.createSbIconView(context, entry) } } override suspend fun activate() = coroutineScope { start() try { awaitCancellation() } finally { stop() } } private fun start() { notifPipeline.addCollectionListener(notifCollectionListener) iconManager.addIconsUpdateListener(iconUpdateRequiredListener) } private fun stop() { notifPipeline.removeCollectionListener(notifCollectionListener) iconManager.removeIconsUpdateListener(iconUpdateRequiredListener) } @AssistedFactory interface Factory { fun create(displayId: Int): ConnectedDisplaysStatusBarNotificationIconViewStore } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt +1 −1 Original line number Diff line number Diff line Loading @@ -53,7 +53,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() { private val shadeRootview = mock<WindowRootView>() private val positionRepository = FakeShadeDisplayRepository() private val shadeContext = mock<Context>() private val contextStore = FakeDisplayWindowPropertiesRepository() private val contextStore = FakeDisplayWindowPropertiesRepository(context) private val testScope = TestScope(UnconfinedTestDispatcher()) private val shadeWm = mock<WindowManager>() private val resources = mock<Resources>() Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStoreTest.kt 0 → 100644 +149 −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.systemui.statusbar.notification.icon.ui.viewbinder import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.display.domain.interactor.displayWindowPropertiesInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifCollection import com.android.systemui.statusbar.notification.collection.notifPipeline import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.icon.iconManager import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) class ConnectedDisplaysStatusBarNotificationIconViewStoreTest : SysuiTestCase() { private val kosmos = testKosmos() private val underTest = ConnectedDisplaysStatusBarNotificationIconViewStore( TEST_DISPLAY_ID, kosmos.notifCollection, kosmos.iconManager, kosmos.displayWindowPropertiesInteractor, kosmos.notifPipeline, ) private val notifCollectionListeners = mutableListOf<NotifCollectionListener>() @Before fun setupNoticCollectionListener() { whenever(kosmos.notifPipeline.addCollectionListener(any())).thenAnswer { invocation -> notifCollectionListeners.add(invocation.arguments[0] as NotifCollectionListener) } } @Before fun activate() { underTest.activateIn(kosmos.testScope) } @Test fun iconView_unknownKey_returnsNull() = kosmos.testScope.runTest { val unknownKey = "unknown key" assertThat(underTest.iconView(unknownKey)).isNull() } @Test fun iconView_knownKey_returnsNonNull() = kosmos.testScope.runTest { val entry = createEntry() whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry) assertThat(underTest.iconView(entry.key)).isNotNull() } @Test fun iconView_knownKey_calledMultipleTimes_returnsSameInstance() = kosmos.testScope.runTest { val entry = createEntry() whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry) val first = underTest.iconView(entry.key) val second = underTest.iconView(entry.key) assertThat(first).isSameInstanceAs(second) } @Test fun iconView_knownKey_afterNotificationRemoved_returnsNewInstance() = kosmos.testScope.runTest { val entry = createEntry() whenever(kosmos.notifCollection.getEntry(entry.key)).thenReturn(entry) val first = underTest.iconView(entry.key) notifCollectionListeners.forEach { it.onEntryRemoved(entry, /* reason= */ 0) } val second = underTest.iconView(entry.key) assertThat(first).isNotSameInstanceAs(second) } private fun createEntry(): NotificationEntry { val channelId = "channelId" val notificationChannel = NotificationChannel(channelId, "name", NotificationManager.IMPORTANCE_DEFAULT) val notification = Notification.Builder(context, channelId) .setContentTitle("Title") .setContentText("Content text") .setSmallIcon(com.android.systemui.res.R.drawable.icon) .build() val statusBarNotification = SbnBuilder().setNotification(notification).build() val ranking = RankingBuilder() .setChannel(notificationChannel) .setKey(statusBarNotification.key) .build() return NotificationEntry( /* sbn = */ statusBarNotification, /* ranking = */ ranking, /* creationTime = */ 1234L, ) } private companion object { const val TEST_DISPLAY_ID = 1234 } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt +5 −1 Original line number Diff line number Diff line Loading @@ -25,7 +25,11 @@ import javax.inject.Inject /** Testable wrapper around Context. */ class IconBuilder @Inject constructor(private val context: Context) { fun createIconView(entry: NotificationEntry): StatusBarIconView { @JvmOverloads fun createIconView( entry: NotificationEntry, context: Context = this.context, ): StatusBarIconView { return StatusBarIconView( context, "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}", Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +54 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.icon import android.app.Notification import android.app.Notification.MessagingStyle import android.app.Person import android.content.Context import android.content.pm.LauncherApps import android.graphics.drawable.Icon import android.os.Build Loading @@ -36,6 +37,7 @@ 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.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.InflationException import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection Loading Loading @@ -68,6 +70,17 @@ constructor( @Background private val bgCoroutineContext: CoroutineContext, @Main private val mainCoroutineContext: CoroutineContext, ) : ConversationIconManager { /** * A listener that is notified when a [NotificationEntry] has been updated and the associated * icons have to be updated as well. */ fun interface OnIconUpdateRequiredListener { fun onIconUpdateRequired(entry: NotificationEntry) } private val onIconUpdateRequiredListeners = mutableSetOf<OnIconUpdateRequiredListener>() private var unimportantConversationKeys: Set<String> = emptySet() /** * A map of running jobs for fetching the person avatar from launcher. The key is the Loading @@ -76,6 +89,16 @@ constructor( private var launcherPeopleAvatarIconJobs: ConcurrentHashMap<String, Job> = ConcurrentHashMap<String, Job>() fun addIconsUpdateListener(listener: OnIconUpdateRequiredListener) { StatusBarConnectedDisplays.assertInNewMode() onIconUpdateRequiredListeners += listener } fun removeIconsUpdateListener(listener: OnIconUpdateRequiredListener) { StatusBarConnectedDisplays.assertInNewMode() onIconUpdateRequiredListeners -= listener } fun attach() { notifCollection.addCollectionListener(entryListener) } Loading Loading @@ -111,6 +134,21 @@ constructor( } } /** * Inflate the [StatusBarIconView] for the given [NotificationEntry], using the specified * [Context]. */ fun createSbIconView(context: Context, entry: NotificationEntry): StatusBarIconView = traceSection("IconManager.createSbIconView") { StatusBarConnectedDisplays.assertInNewMode() val sbIcon = iconBuilder.createIconView(entry, context) sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE val (normalIconDescriptor, _) = getIconDescriptors(entry) setIcon(entry, normalIconDescriptor, sbIcon) return sbIcon } /** * Inflate icon views for each icon variant and assign appropriate icons to them. Stores the * result in [NotificationEntry.getIcons]. Loading Loading @@ -159,6 +197,18 @@ constructor( } } /** Update the [StatusBarIconView] for the given [NotificationEntry]. */ fun updateSbIcon(entry: NotificationEntry, iconView: StatusBarIconView) = traceSection("IconManager.updateSbIcon") { StatusBarConnectedDisplays.assertInNewMode() val (normalIconDescriptor, _) = getIconDescriptors(entry) val notificationContentDescription = entry.sbn.notification?.let { iconBuilder.getIconContentDescription(it) } iconView.setNotification(entry.sbn, notificationContentDescription) setIcon(entry, normalIconDescriptor, iconView) } /** * Update the notification icons. * Loading @@ -172,6 +222,10 @@ constructor( return@traceSection } if (StatusBarConnectedDisplays.isEnabled) { onIconUpdateRequiredListeners.onEach { it.onIconUpdateRequired(entry) } } if (usingCache && !Flags.notificationsBackgroundIcons()) { Log.wtf( TAG, Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/ConnectedDisplaysStatusBarNotificationIconViewStore.kt 0 → 100644 +94 −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.systemui.statusbar.notification.icon.ui.viewbinder import com.android.systemui.display.domain.interactor.DisplayWindowPropertiesInteractor import com.android.systemui.lifecycle.Activatable import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.icon.IconManager import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope /** [IconViewStore] for the status bar on multiple displays. */ class ConnectedDisplaysStatusBarNotificationIconViewStore @AssistedInject constructor( @Assisted private val displayId: Int, private val notifCollection: NotifCollection, private val iconManager: IconManager, private val displayWindowPropertiesInteractor: DisplayWindowPropertiesInteractor, private val notifPipeline: NotifPipeline, ) : IconViewStore, Activatable { private val cachedIcons = ConcurrentHashMap<String, StatusBarIconView>() private val iconUpdateRequiredListener = object : IconManager.OnIconUpdateRequiredListener { override fun onIconUpdateRequired(entry: NotificationEntry) { val iconView = iconView(entry.key) ?: return iconManager.updateSbIcon(entry, iconView) } } private val notifCollectionListener = object : NotifCollectionListener { override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { cachedIcons.remove(entry.key) } } override fun iconView(key: String): StatusBarIconView? { val entry = notifCollection.getEntry(key) ?: return null return cachedIcons.computeIfAbsent(key) { val context = displayWindowPropertiesInteractor.getForStatusBar(displayId).context iconManager.createSbIconView(context, entry) } } override suspend fun activate() = coroutineScope { start() try { awaitCancellation() } finally { stop() } } private fun start() { notifPipeline.addCollectionListener(notifCollectionListener) iconManager.addIconsUpdateListener(iconUpdateRequiredListener) } private fun stop() { notifPipeline.removeCollectionListener(notifCollectionListener) iconManager.removeIconsUpdateListener(iconUpdateRequiredListener) } @AssistedFactory interface Factory { fun create(displayId: Int): ConnectedDisplaysStatusBarNotificationIconViewStore } }