Merge "[flexiglass] SceneContainer in System UI."

This commit is contained in:
Ale Nijamkin
2023-06-16 14:51:24 +00:00
committed by Android (Google) Code Review
16 changed files with 224 additions and 50 deletions

View File

@@ -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" +

View File

@@ -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 },
)
}
}
}
}
}

View File

@@ -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
}

View File

@@ -18,6 +18,7 @@ package com.android.systemui.dagger;
import com.android.systemui.globalactions.ShutdownUiModule;
import com.android.systemui.keyguard.CustomizationProvider;
import com.android.systemui.scene.startable.SceneContainerStartableModule;
import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
@@ -33,6 +34,7 @@ import dagger.Subcomponent;
NotificationInsetsModule.class,
QsFrameTranslateModule.class,
ShutdownUiModule.class,
SceneContainerStartableModule.class,
SystemUIBinder.class,
SystemUIModule.class,
SystemUICoreStartableModule.class,

View File

@@ -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,

View File

@@ -37,6 +37,7 @@ import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
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;
@@ -51,7 +52,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;
@@ -72,6 +72,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;
@@ -183,6 +184,7 @@ import javax.inject.Named;
QRCodeScannerModule.class,
QSFragmentStartableModule.class,
RetailModeModule.class,
SceneContainerFrameworkModule.class,
ScreenshotModule.class,
SensorModule.class,
SecurityRepositoryModule.class,

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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(

View File

@@ -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

View File

@@ -0,0 +1,60 @@
/*
* 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.
* 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.scene.startable
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
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 dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import javax.inject.Inject
import javax.inject.Named
@SysUISingleton
class SceneContainerStartable
@Inject
constructor(
private val view: WindowRootView,
@Named(SceneContainerNames.SYSTEM_UI_DEFAULT) private val viewModel: SceneContainerViewModel,
@Named(SceneContainerNames.SYSTEM_UI_DEFAULT) private val containerConfig: SceneContainerConfig,
@Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
private val scenes: Set<@JvmSuppressWildcards Scene>,
) : CoreStartable {
override fun start() {
(view as? SceneWindowRootView)?.init(
viewModel = viewModel,
containerConfig = containerConfig,
scenes = scenes,
)
}
}
@Module
interface SceneContainerStartableModule {
@Binds
@IntoMap
@ClassKey(SceneContainerStartable::class)
fun bind(impl: SceneContainerStartable): CoreStartable
}

View File

@@ -2,6 +2,73 @@ 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)
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 val lifecycle: Lifecycle =
this@repeatWhenAttached.lifecycle
}
)
addView(
ComposeFacade.createSceneContainerView(
context = context,
viewModel = viewModel,
sceneByKey = sortedSceneByKey,
)
)
}
// Here when destroyed.
removeAllViews()
}
}
}
}

View File

@@ -2,7 +2,37 @@ 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)
/** A view that can serve as the root of the main SysUI window. */
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)
}
}
private fun isRoot(): Boolean {
return parent.let { it !is View || it.id == android.R.id.content }
}
}

View File

@@ -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

View File

@@ -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,6 +29,7 @@ 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
@@ -67,13 +69,16 @@ 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,
): WindowRootView {
return if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
return if (
featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
) {
layoutInflater.inflate(R.layout.scene_window_root, null)
} else {
layoutInflater.inflate(R.layout.super_notification_shade, null)

View File

@@ -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> {