diff options
14 files changed, 196 insertions, 51 deletions
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index cc337459a83c..82fe3f265384 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -24,6 +24,9 @@ import androidx.lifecycle.LifecycleOwner import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel +import com.android.systemui.scene.shared.model.Scene +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.util.time.SystemClock /** The Compose facade, when Compose is *not* available. */ @@ -58,6 +61,14 @@ object ComposeFacade : BaseComposeFacade { throwComposeUnavailableError() } + override fun createSceneContainerView( + context: Context, + viewModel: SceneContainerViewModel, + sceneByKey: Map<SceneKey, Scene>, + ): View { + throwComposeUnavailableError() + } + private fun throwComposeUnavailableError(): Nothing { error( "Compose is not available. Make sure to check isComposeAvailable() before calling any" + diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index 0e79b18b1c24..7926f9224347 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -29,6 +29,11 @@ import com.android.systemui.people.ui.compose.PeopleScreen import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.compose.FooterActions import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel +import com.android.systemui.scene.shared.model.Scene +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.scene.ui.composable.SceneContainer +import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.util.time.SystemClock /** The Compose facade, when Compose is available. */ @@ -71,4 +76,22 @@ object ComposeFacade : BaseComposeFacade { } } } + + override fun createSceneContainerView( + context: Context, + viewModel: SceneContainerViewModel, + sceneByKey: Map<SceneKey, Scene>, + ): View { + return ComposeView(context).apply { + setContent { + PlatformTheme { + SceneContainer( + viewModel = viewModel, + sceneByKey = + sceneByKey.mapValues { (_, scene) -> scene as ComposableScene }, + ) + } + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 4173bdc3c261..b15c60e62ead 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -24,6 +24,9 @@ import androidx.lifecycle.LifecycleOwner import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel +import com.android.systemui.scene.shared.model.Scene +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.util.time.SystemClock /** @@ -66,4 +69,11 @@ interface BaseComposeFacade { viewModel: MultiShadeViewModel, clock: SystemClock, ): View + + /** Create a [View] to represent [viewModel] on screen. */ + fun createSceneContainerView( + context: Context, + viewModel: SceneContainerViewModel, + sceneByKey: Map<SceneKey, Scene>, + ): View } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 2262d8ab2000..f68bd49230d9 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -40,7 +40,6 @@ import com.android.systemui.qs.tileimpl.QSFactoryImpl; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsImplementation; import com.android.systemui.rotationlock.RotationLockModule; -import com.android.systemui.scene.SceneContainerFrameworkModule; import com.android.systemui.screenshot.ReferenceScreenshotModule; import com.android.systemui.settings.dagger.MultiUserUtilsModule; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -104,7 +103,6 @@ import javax.inject.Named; QSModule.class, ReferenceScreenshotModule.class, RotationLockModule.class, - SceneContainerFrameworkModule.class, StatusBarEventsModule.class, StartCentralSurfacesModule.class, VolumeModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 753d55fc22b3..b5d3fafeb3df 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -38,6 +38,7 @@ import com.android.systemui.biometrics.FingerprintReEnrollNotification; import com.android.systemui.biometrics.UdfpsDisplayModeProvider; import com.android.systemui.biometrics.dagger.BiometricsModule; import com.android.systemui.biometrics.dagger.UdfpsModule; +import com.android.systemui.bouncer.ui.BouncerViewModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; import com.android.systemui.common.ui.data.repository.CommonRepositoryModule; @@ -52,7 +53,6 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagsModule; import com.android.systemui.keyboard.KeyboardModule; -import com.android.systemui.bouncer.ui.BouncerViewModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; @@ -73,6 +73,7 @@ import com.android.systemui.qs.QSFragmentStartableModule; import com.android.systemui.qs.footer.dagger.FooterActionsModule; import com.android.systemui.recents.Recents; import com.android.systemui.retail.dagger.RetailModeModule; +import com.android.systemui.scene.SceneContainerFrameworkModule; import com.android.systemui.screenrecord.ScreenRecordModule; import com.android.systemui.screenshot.dagger.ScreenshotModule; import com.android.systemui.security.data.repository.SecurityRepositoryModule; @@ -184,6 +185,7 @@ import javax.inject.Named; QRCodeScannerModule.class, QSFragmentStartableModule.class, RetailModeModule.class, + SceneContainerFrameworkModule.class, ScreenshotModule.class, SensorModule.class, SecurityRepositoryModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 752471d83735..0a9839e2f18b 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -16,7 +16,7 @@ package com.android.systemui.scene -import com.android.systemui.scene.data.model.SceneContainerConfigModule +import com.android.systemui.scene.shared.model.SceneContainerConfigModule import com.android.systemui.scene.ui.composable.SceneModule import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule import dagger.Module diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index 61b162b014d8..1ebeced5fae6 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -16,7 +16,7 @@ package com.android.systemui.scene.data.repository -import com.android.systemui.scene.data.model.SceneContainerConfig +import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import javax.inject.Inject @@ -28,11 +28,9 @@ import kotlinx.coroutines.flow.asStateFlow class SceneContainerRepository @Inject constructor( - containerConfigurations: Set<SceneContainerConfig>, + private val containerConfigByName: Map<String, SceneContainerConfig>, ) { - private val containerConfigByName: Map<String, SceneContainerConfig> = - containerConfigurations.associateBy { config -> config.name } private val containerVisibilityByName: Map<String, MutableStateFlow<Boolean>> = containerConfigByName .map { (containerName, _) -> containerName to MutableStateFlow(true) } @@ -48,21 +46,6 @@ constructor( .map { (containerName, _) -> containerName to MutableStateFlow(1f) } .toMap() - init { - val repeatedContainerNames = - containerConfigurations - .groupingBy { config -> config.name } - .eachCount() - .filter { (_, count) -> count > 1 } - check(repeatedContainerNames.isEmpty()) { - "Container names must be unique. The following container names appear more than once: ${ - repeatedContainerNames - .map { (name, count) -> "\"$name\" appears $count times" } - .joinToString(", ") - }" - } - } - /** * Returns the keys to all scenes in the container with the given name. * diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt index d0769ebe941e..0327edbb06b4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright 2023 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. @@ -14,9 +14,7 @@ * limitations under the License. */ -package com.android.systemui.scene.data.model - -import com.android.systemui.scene.shared.model.SceneKey +package com.android.systemui.scene.shared.model /** Models the configuration of a single scene container. */ data class SceneContainerConfig( diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt index 0af80949f95e..7562a5a848d8 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfigModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright 2023 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. @@ -14,11 +14,9 @@ * limitations under the License. */ -package com.android.systemui.scene.data.model +package com.android.systemui.scene.shared.model import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.scene.shared.model.SceneContainerNames -import com.android.systemui.scene.shared.model.SceneKey import dagger.Module import dagger.Provides import javax.inject.Named diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index 8f001ec6b5e2..9b5898f42279 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -2,6 +2,74 @@ package com.android.systemui.scene.ui.view import android.content.Context import android.util.AttributeSet +import androidx.activity.OnBackPressedDispatcher +import androidx.activity.OnBackPressedDispatcherOwner +import androidx.activity.setViewTreeOnBackPressedDispatcherOwner +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.compose.ComposeFacade +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.scene.shared.model.Scene +import com.android.systemui.scene.shared.model.SceneContainerConfig +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel +import kotlinx.coroutines.launch /** A root view of the main SysUI window that supports scenes. */ -class SceneWindowRootView(context: Context?, attrs: AttributeSet?) : WindowRootView(context, attrs)
\ No newline at end of file +class SceneWindowRootView( + context: Context, + attrs: AttributeSet?, +) : + WindowRootView( + context, + attrs, + ) { + fun init( + viewModel: SceneContainerViewModel, + containerConfig: SceneContainerConfig, + scenes: Set<Scene>, + ) { + val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key } + val sortedSceneByKey: Map<SceneKey, Scene> = buildMap { + containerConfig.sceneKeys.forEach { sceneKey -> + val scene = + checkNotNull(unsortedSceneByKey[sceneKey]) { + "Scene not found for key \"$sceneKey\"!" + } + + put(sceneKey, scene) + } + } + + repeatWhenAttached { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + setViewTreeOnBackPressedDispatcherOwner( + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher = + OnBackPressedDispatcher().apply { + setOnBackInvokedDispatcher(viewRootImpl.onBackInvokedDispatcher) + } + + override fun getLifecycle(): Lifecycle { + return this@repeatWhenAttached.lifecycle + } + } + ) + + addView( + ComposeFacade.createSceneContainerView( + context = context, + viewModel = viewModel, + sceneByKey = sortedSceneByKey, + ) + ) + } + + // Here when destroyed. + removeAllViews() + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt index a0f966705381..636a30141021 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt @@ -2,7 +2,51 @@ package com.android.systemui.scene.ui.view import android.content.Context import android.util.AttributeSet +import android.view.View import android.widget.FrameLayout +import com.android.systemui.compose.ComposeFacade -/** A view that can serve as the root of the main SysUI window. */ -open class WindowRootView(context: Context?, attrs: AttributeSet?) : FrameLayout(context, attrs)
\ No newline at end of file +/** + * A view that can serve as the root of the main SysUI window (but might not, see below for more + * information regarding this confusing comment). + * + * Naturally, only one view may ever be at the root of a view hierarchy tree. Under certain + * conditions, the view hierarchy tree in the scene-containing window view may actually have one + * [WindowRootView] acting as the true root view and another [WindowRootView] which doesn't and is, + * instead, a child of the true root view. To discern which one is which, please use the [isRoot] + * method. + */ +open class WindowRootView( + context: Context, + attrs: AttributeSet?, +) : + FrameLayout( + context, + attrs, + ) { + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + if (ComposeFacade.isComposeAvailable() && isRoot()) { + ComposeFacade.composeInitializer().onAttachedToWindow(this) + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + + if (ComposeFacade.isComposeAvailable() && isRoot()) { + ComposeFacade.composeInitializer().onDetachedFromWindow(this) + } + } + + /** + * Returns `true` if this view is the true root of the view-hierarchy; `false` otherwise. + * + * Please see the class-level documentation to understand why this is possible. + */ + private fun isRoot(): Boolean { + return parent.let { it !is View || it.id == android.R.id.content } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index c9122c77c1d4..2b62b7d67c2a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -58,7 +58,6 @@ import android.widget.FrameLayout; import com.android.internal.view.FloatingActionMode; import com.android.internal.widget.floatingtoolbar.FloatingToolbar; import com.android.systemui.R; -import com.android.systemui.compose.ComposeFacade; import com.android.systemui.scene.ui.view.WindowRootView; /** @@ -149,18 +148,6 @@ public class NotificationShadeWindowView extends WindowRootView { protected void onAttachedToWindow() { super.onAttachedToWindow(); setWillNotDraw(!DEBUG); - - if (ComposeFacade.INSTANCE.isComposeAvailable()) { - ComposeFacade.INSTANCE.composeInitializer().onAttachedToWindow(this); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (ComposeFacade.INSTANCE.isComposeAvailable()) { - ComposeFacade.INSTANCE.composeInitializer().onDetachedFromWindow(this); - } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 4b2a65f36aa1..a2b93516695a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade +import android.annotation.SuppressLint import android.content.ContentResolver import android.os.Handler import android.view.LayoutInflater @@ -28,13 +29,19 @@ import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.biometrics.AuthRippleController import com.android.systemui.biometrics.AuthRippleView +import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.privacy.OngoingPrivacyChip +import com.android.systemui.scene.shared.model.Scene +import com.android.systemui.scene.shared.model.SceneContainerConfig +import com.android.systemui.scene.shared.model.SceneContainerNames +import com.android.systemui.scene.ui.view.SceneWindowRootView import com.android.systemui.scene.ui.view.WindowRootView +import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShelf @@ -67,14 +74,30 @@ abstract class ShadeModule { companion object { const val SHADE_HEADER = "large_screen_shade_header" + @SuppressLint("InflateParams") // Root views don't have parents. @Provides @SysUISingleton fun providesWindowRootView( layoutInflater: LayoutInflater, featureFlags: FeatureFlags, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + viewModelProvider: Provider<SceneContainerViewModel>, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + containerConfigProvider: Provider<SceneContainerConfig>, + @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) + scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>, ): WindowRootView { - return if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) { - layoutInflater.inflate(R.layout.scene_window_root, null) + return if ( + featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable() + ) { + val sceneWindowRootView = + layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView + sceneWindowRootView.init( + viewModel = viewModelProvider.get(), + containerConfig = containerConfigProvider.get(), + scenes = scenesProvider.get(), + ) + sceneWindowRootView } else { layoutInflater.inflate(R.layout.super_notification_shade, null) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 1b7542b42128..9c4fd9459b57 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -24,9 +24,9 @@ import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor -import com.android.systemui.scene.data.model.SceneContainerConfig import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -51,7 +51,7 @@ class SceneTestUtils( fakeSceneContainerConfig(CONTAINER_2), ) ): SceneContainerRepository { - return SceneContainerRepository(containerConfigurations) + return SceneContainerRepository(containerConfigurations.associateBy { it.name }) } fun fakeSceneKeys(): List<SceneKey> { |