Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0407508c authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[flexiglass] Drive window view visibility.

When the internal state of our scene system is updated, the visibility
of the window view that contains our scene graph is driven by the new
startable.

Bug: 290656769
Test: added unit tests
Test: manually verified that, when the feature flag is enabled, the
visibility calls coming from legacy code into
WindowRootView#setVisibility are properly ignored and the visibility of
the root view is driven by the new system instead.
Test: manually verified no harm done. When the feature flag is disabled,
the visibility of the window root view is still updated by the legacy
code.

Change-Id: I20dccfdc7089dda0732b56c384d139e79a4c2f96
parent db7568e3
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.scene

import com.android.systemui.scene.domain.startable.SceneContainerStartableModule
import com.android.systemui.scene.shared.model.SceneContainerConfigModule
import com.android.systemui.scene.ui.composable.SceneModule
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule
@@ -25,6 +26,7 @@ import dagger.Module
    includes =
        [
            SceneContainerConfigModule::class,
            SceneContainerStartableModule::class,
            SceneContainerViewModelModule::class,
            SceneModule::class,
        ],
+11 −1
Original line number Diff line number Diff line
@@ -24,7 +24,17 @@ import com.android.systemui.scene.shared.model.SceneTransitionModel
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow

/** Business logic and app state accessors for the scene framework. */
/**
 * Generic business logic and app state accessors for the scene framework.
 *
 * Note that scene container specific business logic does not belong in this class. Instead, it
 * should be hoisted to a class that is specific to that scene container, for an example, please see
 * [SystemUiDefaultSceneContainerStartable].
 *
 * Also note that this class should not depend on state or logic of other modules or features.
 * Instead, other feature modules should depend on and call into this class when their parts of the
 * application state change.
 */
@SysUISingleton
class SceneInteractor
@Inject
+32 −0
Original line number Diff line number Diff line
/*
 * 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.domain.startable

import com.android.systemui.CoreStartable
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap

@Module
interface SceneContainerStartableModule {

    @Binds
    @IntoMap
    @ClassKey(SystemUiDefaultSceneContainerStartable::class)
    fun bind(impl: SystemUiDefaultSceneContainerStartable): CoreStartable
}
+69 −0
Original line number Diff line number Diff line
/*
 * 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.domain.startable

import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.scene.shared.model.SceneKey
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

/**
 * Hooks up business logic that manipulates the state of the [SceneInteractor] for the default
 * system UI scene container (the one named [SceneContainerNames.SYSTEM_UI_DEFAULT]) based on state
 * from other systems.
 */
@SysUISingleton
class SystemUiDefaultSceneContainerStartable
@Inject
constructor(
    @Application private val applicationScope: CoroutineScope,
    private val sceneInteractor: SceneInteractor,
    private val featureFlags: FeatureFlags,
) : CoreStartable {

    override fun start() {
        if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
            keepVisibilityUpdated()
        }
    }

    /** Drives visibility of the scene container. */
    private fun keepVisibilityUpdated() {
        applicationScope.launch {
            sceneInteractor
                .currentScene(CONTAINER_NAME)
                .map { it.key }
                .distinctUntilChanged()
                .collect { sceneKey ->
                    sceneInteractor.setVisible(CONTAINER_NAME, sceneKey != SceneKey.Gone)
                }
        }
    }

    companion object {
        private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT
    }
}
+13 −48
Original line number Diff line number Diff line
@@ -2,19 +2,10 @@ 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 android.view.View
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(
@@ -30,45 +21,19 @@ class SceneWindowRootView(
        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,
        SceneWindowRootViewBinder.bind(
            view = this@SceneWindowRootView,
            viewModel = viewModel,
                            sceneByKey = sortedSceneByKey,
                        )
            containerConfig = containerConfig,
            scenes = scenes,
            onVisibilityChangedInternal = { isVisible ->
                super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
            }
        )
    }

                // Here when destroyed.
                removeAllViews()
            }
        }
    override fun setVisibility(visibility: Int) {
        // Do nothing. We don't want external callers to invoke this. Instead, we drive our own
        // visibility from our view-binder.
    }
}
Loading