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

Commit 5dda9113 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[flexiglass] Compose version of KeyguardRootView.

1. In order to be able to take advantage of shared element transition
   animations when switching between Flexiglass scenes, we need to have
   an unbroken chain of @Composable functions from the root at
   SceneContainer all the way down to the immediate parent of each of
   those shared elements.
2. In order to enable that ability for media (UMO) on the
   LockscreenScene, we must replace KeyguardRootView with an @Composable
   equivalent, this CL does that.
3. Because Jetpack Compose doesn't come with built-in support for
   ConstraintLayout (unless we take on a resource-heavy dependency on
   Compose's ConstraintLayout) and because the performance and
   readability benefits that ConstraintLayout brings to View-based code
   are not applicable to Compose-based code, this CL also replaces the
   existing blueprint/section ConstraintSet-dependent framework with a
   new blueprint/section framework that's pure Compose.
4. As a demonstration, the CL includes the default blueprint
   implementation and two real sections: status bar and bottom area. The
   rest are rendered as placeholder color boxes but should be
   relatively easy to add.
5. Finally, to switch between the KeyguardRootView implementation and
   the new Compose-based implementation, set the UseLockscreenContent
   constant in LockscreenScene.kt

Bug: 316211368
Test: manually verified that the default blueprint loosk more or less
correct (see https://screenshot.googleplex.com/64Y8NaTAHiFddYs for what
it looks like on a foldable).
Test: used the blueprint switching command adb shell cmd statusbar
blueprint to manually verify that switching between blueprints works and
goes to the other, placeholder/TODO blueprints as intended
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT

Change-Id: Ife687d6f2a5fcb6680cc769d9d8e3aad05e63956
parent ff50b586
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewConfigurator
import com.android.systemui.keyguard.qualifiers.KeyguardRootView
import com.android.systemui.keyguard.ui.composable.LockscreenScene
import com.android.systemui.keyguard.ui.composable.LockscreenSceneBlueprintModule
import com.android.systemui.scene.shared.model.Scene
import dagger.Binds
import dagger.Module
@@ -29,7 +30,12 @@ import dagger.multibindings.IntoSet
import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi

@Module
@Module(
    includes =
        [
            LockscreenSceneBlueprintModule::class,
        ],
)
interface LockscreenSceneModule {

    @Binds @IntoSet fun lockscreenScene(scene: LockscreenScene): Scene
+73 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.keyguard.ui.composable

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.transitions
import com.android.systemui.keyguard.ui.composable.blueprint.LockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import javax.inject.Inject

/**
 * Renders the content of the lockscreen.
 *
 * This is separate from the [LockscreenScene] because it's meant to support usage of this UI from
 * outside the scene container framework.
 */
class LockscreenContent
@Inject
constructor(
    private val viewModel: LockscreenContentViewModel,
    private val blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
) {

    private val sceneKeyByBlueprint: Map<LockscreenSceneBlueprint, SceneKey> by lazy {
        blueprints.associateWith { blueprint -> SceneKey(blueprint.id) }
    }
    private val sceneKeyByBlueprintId: Map<String, SceneKey> by lazy {
        sceneKeyByBlueprint.entries.associate { (blueprint, sceneKey) -> blueprint.id to sceneKey }
    }

    @Composable
    fun Content(
        modifier: Modifier = Modifier,
    ) {
        val coroutineScope = rememberCoroutineScope()
        val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState()

        // Switch smoothly between blueprints, any composable tagged with element() will be
        // transition-animated between any two blueprints, if they both display the same element.
        SceneTransitionLayout(
            currentScene = checkNotNull(sceneKeyByBlueprintId[blueprintId]),
            onChangeScene = {},
            transitions =
                transitions { sceneKeyByBlueprintId.values.forEach { sceneKey -> to(sceneKey) } },
            modifier = modifier,
        ) {
            sceneKeyByBlueprint.entries.forEach { (blueprint, sceneKey) ->
                scene(sceneKey) { with(blueprint) { Content(Modifier.fillMaxSize()) } }
            }
        }
    }
}
+26 −12
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@
 * limitations under the License.
 */

@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
@file:OptIn(ExperimentalFoundationApi::class)

package com.android.systemui.keyguard.ui.composable

@@ -50,14 +50,17 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.ui.composable.ComposableScene
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/** Set this to `true` to use the LockscreenContent replacement of KeyguardRootView. */
private val UseLockscreenContent = false

/** The lock screen scene shows when the device is locked. */
@SysUISingleton
class LockscreenScene
@@ -66,6 +69,7 @@ constructor(
    @Application private val applicationScope: CoroutineScope,
    private val viewModel: LockscreenSceneViewModel,
    @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
    private val lockscreenContent: Lazy<LockscreenContent>,
) : ComposableScene {
    override val key = SceneKey.Lockscreen

@@ -91,6 +95,7 @@ constructor(
        LockscreenScene(
            viewProvider = viewProvider,
            viewModel = viewModel,
            lockscreenContent = lockscreenContent,
            modifier = modifier,
        )
    }
@@ -113,6 +118,7 @@ constructor(
private fun SceneScope.LockscreenScene(
    viewProvider: () -> View,
    viewModel: LockscreenSceneViewModel,
    lockscreenContent: Lazy<LockscreenContent>,
    modifier: Modifier = Modifier,
) {
    fun findSettingsMenu(): View {
@@ -133,16 +139,24 @@ private fun SceneScope.LockscreenScene(
            modifier = Modifier.fillMaxSize(),
        )

        if (UseLockscreenContent) {
            lockscreenContent
                .get()
                .Content(
                    modifier = Modifier.fillMaxSize(),
                )
        } else {
            AndroidView(
                factory = { _ ->
                    val keyguardRootView = viewProvider()
                // Remove the KeyguardRootView from any parent it might already have in legacy code
                // just in case (a view can't have two parents).
                    // Remove the KeyguardRootView from any parent it might already have in legacy
                    // code just in case (a view can't have two parents).
                    (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
                    keyguardRootView
                },
                modifier = Modifier.fillMaxSize(),
            )
        }

        val notificationStackPosition by viewModel.keyguardRoot.notificationBounds.collectAsState()

+34 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.keyguard.ui.composable

import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule
import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeBlueprintModule
import dagger.Module

@Module(
    includes =
        [
            CommunalBlueprintModule::class,
            DefaultBlueprintModule::class,
            ShortcutsBesideUdfpsBlueprintModule::class,
            SplitShadeBlueprintModule::class,
        ],
)
interface LockscreenSceneBlueprintModule
+52 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.keyguard.ui.composable.blueprint

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.animation.scene.SceneScope
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import javax.inject.Inject

/** Renders the lockscreen scene when showing the communal glanceable hub. */
class CommunalBlueprint @Inject constructor() : LockscreenSceneBlueprint {

    override val id: String = "communal"

    @Composable
    override fun SceneScope.Content(modifier: Modifier) {
        Box(modifier.background(Color.Black)) {
            Text(
                text = "TODO(b/316211368): communal blueprint",
                color = Color.White,
                modifier = Modifier.align(Alignment.Center),
            )
        }
    }
}

@Module
interface CommunalBlueprintModule {
    @Binds @IntoSet fun blueprint(blueprint: CommunalBlueprint): LockscreenSceneBlueprint
}
Loading