Loading packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/LocalStatusIconContext.kt 0 → 100644 +91 −0 Original line number Diff line number Diff line /* * 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.systemui.shade.ui.composable import android.view.ContextThemeWrapper import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import com.android.compose.animation.scene.ContentKey import com.android.systemui.res.R import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.ui.TintedIconManager /** * Defines interface for classes that can provide a context for UI that renders the status bar * icons. */ interface StatusIconContext { /** * Returns a [StatusIconContainer] for the given [contentKey]. Note that this container will be * cached in the [StatusIconContext]. */ fun iconContainer(contentKey: ContentKey): StatusIconContainer /** * Returns a [TintedIconManager] for the given [contentKey]. Note that this manager will be * cached in the [StatusIconContext]. */ fun iconManager(contentKey: ContentKey): TintedIconManager } val LocalStatusIconContext = compositionLocalOf<StatusIconContext> { error("LocalStatusIconContext not set!") } @Composable fun WithStatusIconContext( tintedIconManagerFactory: TintedIconManager.Factory, block: @Composable () -> Unit, ) { val localContext = LocalContext.current val statusIconContext = remember(localContext, tintedIconManagerFactory) { object : StatusIconContext { private val iconContainerByContentKey = mutableMapOf<ContentKey, StatusIconContainer>() private val iconManagerByContentKey = mutableMapOf<ContentKey, TintedIconManager>() override fun iconContainer(contentKey: ContentKey): StatusIconContainer { return iconContainerByContentKey.getOrPut(contentKey) { StatusIconContainer( ContextThemeWrapper( localContext, R.style.Theme_SystemUI_QuickSettings_Header, ), null, ) } } override fun iconManager(contentKey: ContentKey): TintedIconManager { return iconManagerByContentKey.getOrPut(contentKey) { tintedIconManagerFactory.create( iconContainer(contentKey), StatusBarLocation.QS, ) } } } } CompositionLocalProvider(LocalStatusIconContext provides statusIconContext, content = block) } packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +4 −5 Original line number Diff line number Diff line Loading @@ -100,7 +100,6 @@ import com.android.systemui.shade.ui.composable.ShadeHeader.Values.ClockScale import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.statusbar.core.NewStatusBarIcons import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.pipeline.battery.ui.composable.BatteryWithEstimate import com.android.systemui.statusbar.pipeline.mobile.StatusBarMobileIconKairos import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView Loading Loading @@ -724,10 +723,9 @@ private fun ContentScope.StatusIcons( val micSlot = stringResource(id = com.android.internal.R.string.status_bar_microphone) val locationSlot = stringResource(id = com.android.internal.R.string.status_bar_location) val iconContainer = remember { StatusIconContainer(themedContext, null) } val iconManager = remember { viewModel.createTintedIconManager(iconContainer, StatusBarLocation.QS) } val statusIconContext = LocalStatusIconContext.current val iconContainer = statusIconContext.iconContainer(contentKey) val iconManager = statusIconContext.iconManager(contentKey) // TODO(408001821): Use composable system status icons here instead. AndroidView( Loading @@ -737,6 +735,7 @@ private fun ContentScope.StatusIcons( iconContainer }, onRelease = { viewModel.statusBarIconController.removeIconGroup(iconManager) }, update = { iconContainer -> iconContainer.setQsExpansionTransitioning( layoutState.isTransitioningBetween(Scenes.Shade, Scenes.QuickSettings) Loading packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +3 −0 Original line number Diff line number Diff line Loading @@ -13,6 +13,7 @@ import com.android.systemui.scene.ui.composable.Scene import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.shade.TouchLogger import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.phone.ui.TintedIconManager import javax.inject.Provider import kotlinx.coroutines.flow.MutableStateFlow Loading @@ -34,6 +35,7 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi qsSceneAdapter: Provider<QSSceneAdapter>, sceneJankMonitorFactory: SceneJankMonitor.Factory, windowRootViewKeyEventHandler: WindowRootViewKeyEventHandler, tintedIconManagerFactory: TintedIconManager.Factory, ) { setLayoutInsetsController(layoutInsetController) SceneWindowRootViewBinder.bind( Loading @@ -53,6 +55,7 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi dataSourceDelegator = sceneDataSourceDelegator, qsSceneAdapter = qsSceneAdapter, sceneJankMonitorFactory = sceneJankMonitorFactory, tintedIconManagerFactory = tintedIconManagerFactory, ) setWindowRootViewKeyEventHandler(windowRootViewKeyEventHandler) } Loading packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +18 −11 Original line number Diff line number Diff line Loading @@ -50,7 +50,9 @@ import com.android.systemui.scene.ui.composable.Scene import com.android.systemui.scene.ui.composable.SceneContainer import com.android.systemui.scene.ui.viewmodel.DualShadeEducationalTooltipsViewModel import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.shade.ui.composable.WithStatusIconContext import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.phone.ui.TintedIconManager import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.awaitCancellation Loading @@ -76,6 +78,7 @@ object SceneWindowRootViewBinder { dataSourceDelegator: SceneDataSourceDelegator, qsSceneAdapter: Provider<QSSceneAdapter>, sceneJankMonitorFactory: SceneJankMonitor.Factory, tintedIconManagerFactory: TintedIconManager.Factory, ) { val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key } val sortedSceneByKey: Map<SceneKey, Scene> = Loading Loading @@ -128,6 +131,7 @@ object SceneWindowRootViewBinder { qsSceneAdapter = qsSceneAdapter, containerConfig = containerConfig, sceneJankMonitorFactory = sceneJankMonitorFactory, tintedIconManagerFactory = tintedIconManagerFactory, ) .also { it.id = R.id.scene_container_root_composable } ) Loading Loading @@ -175,6 +179,7 @@ object SceneWindowRootViewBinder { qsSceneAdapter: Provider<QSSceneAdapter>, containerConfig: SceneContainerConfig, sceneJankMonitorFactory: SceneJankMonitor.Factory, tintedIconManagerFactory: TintedIconManager.Factory, ): View { return ComposeView(context).apply { setContent { Loading @@ -183,6 +188,7 @@ object SceneWindowRootViewBinder { displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets), screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context), ) { WithStatusIconContext(tintedIconManagerFactory = tintedIconManagerFactory) { SceneContainer( viewModel = viewModel, sceneByKey = sceneByKey, Loading @@ -199,6 +205,7 @@ object SceneWindowRootViewBinder { } } } } private fun createDualShadeEducationalTooltipsView( scope: CoroutineScope, Loading packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +3 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificat import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.TapAgainView import com.android.systemui.statusbar.phone.ui.TintedIconManager import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.tuner.TunerService Loading Loading @@ -99,6 +100,7 @@ abstract class ShadeViewProviderModule { blurUtils: BlurUtils, choreographer: Choreographer?, @Main mainDispatcher: CoroutineDispatcher, tintedIconManagerFactory: TintedIconManager.Factory, ): WindowRootView { return if (SceneContainerFlag.isEnabled) { checkNoSceneDuplicates(scenesProvider.get()) Loading @@ -123,6 +125,7 @@ abstract class ShadeViewProviderModule { qsSceneAdapter = qsSceneAdapter, sceneJankMonitorFactory = sceneJankMonitorFactory, windowRootViewKeyEventHandler = windowRootViewKeyEventHandler, tintedIconManagerFactory = tintedIconManagerFactory, ) sceneWindowRootView } else { Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/LocalStatusIconContext.kt 0 → 100644 +91 −0 Original line number Diff line number Diff line /* * 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.systemui.shade.ui.composable import android.view.ContextThemeWrapper import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import com.android.compose.animation.scene.ContentKey import com.android.systemui.res.R import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.ui.TintedIconManager /** * Defines interface for classes that can provide a context for UI that renders the status bar * icons. */ interface StatusIconContext { /** * Returns a [StatusIconContainer] for the given [contentKey]. Note that this container will be * cached in the [StatusIconContext]. */ fun iconContainer(contentKey: ContentKey): StatusIconContainer /** * Returns a [TintedIconManager] for the given [contentKey]. Note that this manager will be * cached in the [StatusIconContext]. */ fun iconManager(contentKey: ContentKey): TintedIconManager } val LocalStatusIconContext = compositionLocalOf<StatusIconContext> { error("LocalStatusIconContext not set!") } @Composable fun WithStatusIconContext( tintedIconManagerFactory: TintedIconManager.Factory, block: @Composable () -> Unit, ) { val localContext = LocalContext.current val statusIconContext = remember(localContext, tintedIconManagerFactory) { object : StatusIconContext { private val iconContainerByContentKey = mutableMapOf<ContentKey, StatusIconContainer>() private val iconManagerByContentKey = mutableMapOf<ContentKey, TintedIconManager>() override fun iconContainer(contentKey: ContentKey): StatusIconContainer { return iconContainerByContentKey.getOrPut(contentKey) { StatusIconContainer( ContextThemeWrapper( localContext, R.style.Theme_SystemUI_QuickSettings_Header, ), null, ) } } override fun iconManager(contentKey: ContentKey): TintedIconManager { return iconManagerByContentKey.getOrPut(contentKey) { tintedIconManagerFactory.create( iconContainer(contentKey), StatusBarLocation.QS, ) } } } } CompositionLocalProvider(LocalStatusIconContext provides statusIconContext, content = block) }
packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +4 −5 Original line number Diff line number Diff line Loading @@ -100,7 +100,6 @@ import com.android.systemui.shade.ui.composable.ShadeHeader.Values.ClockScale import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.statusbar.core.NewStatusBarIcons import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.pipeline.battery.ui.composable.BatteryWithEstimate import com.android.systemui.statusbar.pipeline.mobile.StatusBarMobileIconKairos import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView Loading Loading @@ -724,10 +723,9 @@ private fun ContentScope.StatusIcons( val micSlot = stringResource(id = com.android.internal.R.string.status_bar_microphone) val locationSlot = stringResource(id = com.android.internal.R.string.status_bar_location) val iconContainer = remember { StatusIconContainer(themedContext, null) } val iconManager = remember { viewModel.createTintedIconManager(iconContainer, StatusBarLocation.QS) } val statusIconContext = LocalStatusIconContext.current val iconContainer = statusIconContext.iconContainer(contentKey) val iconManager = statusIconContext.iconManager(contentKey) // TODO(408001821): Use composable system status icons here instead. AndroidView( Loading @@ -737,6 +735,7 @@ private fun ContentScope.StatusIcons( iconContainer }, onRelease = { viewModel.statusBarIconController.removeIconGroup(iconManager) }, update = { iconContainer -> iconContainer.setQsExpansionTransitioning( layoutState.isTransitioningBetween(Scenes.Shade, Scenes.QuickSettings) Loading
packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +3 −0 Original line number Diff line number Diff line Loading @@ -13,6 +13,7 @@ import com.android.systemui.scene.ui.composable.Scene import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.shade.TouchLogger import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.phone.ui.TintedIconManager import javax.inject.Provider import kotlinx.coroutines.flow.MutableStateFlow Loading @@ -34,6 +35,7 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi qsSceneAdapter: Provider<QSSceneAdapter>, sceneJankMonitorFactory: SceneJankMonitor.Factory, windowRootViewKeyEventHandler: WindowRootViewKeyEventHandler, tintedIconManagerFactory: TintedIconManager.Factory, ) { setLayoutInsetsController(layoutInsetController) SceneWindowRootViewBinder.bind( Loading @@ -53,6 +55,7 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi dataSourceDelegator = sceneDataSourceDelegator, qsSceneAdapter = qsSceneAdapter, sceneJankMonitorFactory = sceneJankMonitorFactory, tintedIconManagerFactory = tintedIconManagerFactory, ) setWindowRootViewKeyEventHandler(windowRootViewKeyEventHandler) } Loading
packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +18 −11 Original line number Diff line number Diff line Loading @@ -50,7 +50,9 @@ import com.android.systemui.scene.ui.composable.Scene import com.android.systemui.scene.ui.composable.SceneContainer import com.android.systemui.scene.ui.viewmodel.DualShadeEducationalTooltipsViewModel import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.shade.ui.composable.WithStatusIconContext import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.phone.ui.TintedIconManager import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.awaitCancellation Loading @@ -76,6 +78,7 @@ object SceneWindowRootViewBinder { dataSourceDelegator: SceneDataSourceDelegator, qsSceneAdapter: Provider<QSSceneAdapter>, sceneJankMonitorFactory: SceneJankMonitor.Factory, tintedIconManagerFactory: TintedIconManager.Factory, ) { val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key } val sortedSceneByKey: Map<SceneKey, Scene> = Loading Loading @@ -128,6 +131,7 @@ object SceneWindowRootViewBinder { qsSceneAdapter = qsSceneAdapter, containerConfig = containerConfig, sceneJankMonitorFactory = sceneJankMonitorFactory, tintedIconManagerFactory = tintedIconManagerFactory, ) .also { it.id = R.id.scene_container_root_composable } ) Loading Loading @@ -175,6 +179,7 @@ object SceneWindowRootViewBinder { qsSceneAdapter: Provider<QSSceneAdapter>, containerConfig: SceneContainerConfig, sceneJankMonitorFactory: SceneJankMonitor.Factory, tintedIconManagerFactory: TintedIconManager.Factory, ): View { return ComposeView(context).apply { setContent { Loading @@ -183,6 +188,7 @@ object SceneWindowRootViewBinder { displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets), screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context), ) { WithStatusIconContext(tintedIconManagerFactory = tintedIconManagerFactory) { SceneContainer( viewModel = viewModel, sceneByKey = sceneByKey, Loading @@ -199,6 +205,7 @@ object SceneWindowRootViewBinder { } } } } private fun createDualShadeEducationalTooltipsView( scope: CoroutineScope, Loading
packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt +3 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificat import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.TapAgainView import com.android.systemui.statusbar.phone.ui.TintedIconManager import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.tuner.TunerService Loading Loading @@ -99,6 +100,7 @@ abstract class ShadeViewProviderModule { blurUtils: BlurUtils, choreographer: Choreographer?, @Main mainDispatcher: CoroutineDispatcher, tintedIconManagerFactory: TintedIconManager.Factory, ): WindowRootView { return if (SceneContainerFlag.isEnabled) { checkNoSceneDuplicates(scenesProvider.get()) Loading @@ -123,6 +125,7 @@ abstract class ShadeViewProviderModule { qsSceneAdapter = qsSceneAdapter, sceneJankMonitorFactory = sceneJankMonitorFactory, windowRootViewKeyEventHandler = windowRootViewKeyEventHandler, tintedIconManagerFactory = tintedIconManagerFactory, ) sceneWindowRootView } else { Loading