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

Commit c3c40b46 authored by Ale Nijamkin's avatar Ale Nijamkin
Browse files

[flexiglass] Break up lockscreen sections into elements

The concept of "section" comes from the non-Compose version of the
lockscreen that used ConstraintLayout and is no longer relevant or
useful in the world of a Compose-based custom layout where each element
is measured, placed, and drawn by a centralized slot-based
LockscreenSceneLayout.

Bug: 395060439
Bug: 405383415
Bug: 396218588
Bug: 396202921
Bug: 384077475
Bug: 394875986
Bug: 405366494
Bug: 390988286
Test: manually verified no visible changes
Flag: com.android.systemui.scene_container
Change-Id: Ia7c7537767fec66bbbd97fe531ed6425a1b4541d
parent f4b87662
Loading
Loading
Loading
Loading
+6 −8
Original line number Diff line number Diff line
@@ -35,15 +35,14 @@ import com.android.compose.animation.scene.ContentScope
import com.android.systemui.Flags
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
import com.android.systemui.communal.ui.compose.section.CommunalLockSection
import com.android.systemui.communal.ui.compose.section.CommunalPopupSection
import com.android.systemui.communal.ui.compose.section.HubOnboardingSection
import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.keyguard.ui.composable.element.IndicationAreaElement
import com.android.systemui.keyguard.ui.composable.element.LockElement
import com.android.systemui.keyguard.ui.composable.layout.LockIconAlignmentLines
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
@@ -58,10 +57,9 @@ constructor(
    private val interactionHandler: SmartspaceInteractionHandler,
    private val communalSettingsInteractor: CommunalSettingsInteractor,
    private val dialogFactory: SystemUIDialogFactory,
    private val lockSection: LockSection,
    private val lockElement: LockElement,
    private val communalLockSection: CommunalLockSection,
    private val bottomAreaSection: BottomAreaSection,
    private val ambientStatusBarSection: AmbientStatusBarSection,
    private val indicationAreaElement: IndicationAreaElement,
    private val communalPopupSection: CommunalPopupSection,
    private val widgetSection: CommunalAppWidgetSection,
    private val hubOnboardingSection: HubOnboardingSection,
@@ -91,14 +89,14 @@ constructor(
                            LockIcon(modifier = Modifier.element(Communal.Elements.LockIcon))
                        }
                    } else {
                        with(lockSection) {
                        with(lockElement) {
                            LockIcon(
                                overrideColor = MaterialTheme.colorScheme.onPrimaryContainer,
                                modifier = Modifier.element(Communal.Elements.LockIcon),
                            )
                        }
                    }
                    with(bottomAreaSection) {
                    with(indicationAreaElement) {
                        IndicationArea(
                            Modifier.element(Communal.Elements.IndicationArea)
                                .fillMaxWidth()
+2 −8
Original line number Diff line number Diff line
@@ -17,14 +17,8 @@
package com.android.systemui.keyguard.ui.composable

import com.android.systemui.keyguard.ui.composable.blueprint.CommunalBlueprintModule
import com.android.systemui.keyguard.ui.composable.section.OptionalSectionModule
import com.android.systemui.keyguard.ui.composable.element.OptionalElementModule
import dagger.Module

@Module(
    includes =
        [
            CommunalBlueprintModule::class,
            OptionalSectionModule::class,
        ],
)
@Module(includes = [CommunalBlueprintModule::class, OptionalElementModule::class])
interface LockscreenSceneBlueprintModule
+47 −35
Original line number Diff line number Diff line
@@ -23,16 +23,21 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.ContentScope
import com.android.systemui.keyguard.ui.composable.LockscreenTouchHandling
import com.android.systemui.keyguard.ui.composable.element.AmbientIndicationElement
import com.android.systemui.keyguard.ui.composable.element.AodPromotedNotificationAreaElement
import com.android.systemui.keyguard.ui.composable.element.DateAndWeatherElement
import com.android.systemui.keyguard.ui.composable.element.HeadsUpNotificationsElement
import com.android.systemui.keyguard.ui.composable.element.IndicationAreaElement
import com.android.systemui.keyguard.ui.composable.element.LargeClockElement
import com.android.systemui.keyguard.ui.composable.element.LockElement
import com.android.systemui.keyguard.ui.composable.element.MediaCarouselElement
import com.android.systemui.keyguard.ui.composable.element.NotificationElement
import com.android.systemui.keyguard.ui.composable.element.SettingsMenuElement
import com.android.systemui.keyguard.ui.composable.element.ShortcutElement
import com.android.systemui.keyguard.ui.composable.element.SmallClockElement
import com.android.systemui.keyguard.ui.composable.element.SmartSpaceElement
import com.android.systemui.keyguard.ui.composable.element.StatusBarElement
import com.android.systemui.keyguard.ui.composable.layout.LockscreenSceneLayout
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import java.util.Optional
@@ -45,16 +50,21 @@ import javax.inject.Inject
class DefaultBlueprint
@Inject
constructor(
    private val statusBarSection: StatusBarSection,
    private val lockSection: LockSection,
    private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
    private val bottomAreaSection: BottomAreaSection,
    private val settingsMenuSection: SettingsMenuSection,
    private val notificationSection: NotificationSection,
    private val clockSection: DefaultClockSection,
    private val statusBarElement: StatusBarElement,
    private val lockElement: LockElement,
    private val ambientIndicationElementOptional: Optional<AmbientIndicationElement>,
    private val shortcutElement: ShortcutElement,
    private val indicationAreaElement: IndicationAreaElement,
    private val settingsMenuElement: SettingsMenuElement,
    private val headsUpNotificationsElement: HeadsUpNotificationsElement,
    private val notificationsElement: NotificationElement,
    private val aodPromotedNotificationAreaElement: AodPromotedNotificationAreaElement,
    private val smallClockElement: SmallClockElement,
    private val largeClockElement: LargeClockElement,
    private val keyguardClockViewModel: KeyguardClockViewModel,
    private val smartSpaceSection: SmartSpaceSection,
    private val mediaSection: MediaCarouselSection,
    private val smartSpaceElement: SmartSpaceElement,
    private val dateAndWeatherElement: DateAndWeatherElement,
    private val mediaCarouselElement: MediaCarouselElement,
) : ComposableLockscreenSceneBlueprint {

    override val id: String = "default"
@@ -64,7 +74,7 @@ constructor(
        val isBypassEnabled = viewModel.isBypassEnabled

        if (isBypassEnabled) {
            with(notificationSection) { HeadsUpNotifications() }
            with(headsUpNotificationsElement) { HeadsUpNotifications() }
        }

        LockscreenTouchHandling(
@@ -76,10 +86,10 @@ constructor(
            LockscreenSceneLayout(
                viewModel = viewModel.layout,
                statusBar = {
                    with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
                    with(statusBarElement) { StatusBar(modifier = Modifier.fillMaxWidth()) }
                },
                smallClock = {
                    with(clockSection) {
                    with(smallClockElement) {
                        SmallClock(
                            burnInParams = burnIn.parameters,
                            onTopChanged = burnIn.onSmallClockTopChanged,
@@ -87,13 +97,13 @@ constructor(
                    }
                },
                largeClock = {
                    with(clockSection) { LargeClock(burnInParams = burnIn.parameters) }
                    with(largeClockElement) { LargeClock(burnInParams = burnIn.parameters) }
                },
                dateAndWeather = { orientation ->
                    with(smartSpaceSection) { DateAndWeather(orientation) }
                    with(dateAndWeatherElement) { DateAndWeather(orientation) }
                },
                smartSpace = {
                    with(smartSpaceSection) {
                    with(smartSpaceElement) {
                        SmartSpace(
                            burnInParams = burnIn.parameters,
                            onTopChanged = burnIn.onSmartspaceTopChanged,
@@ -102,37 +112,39 @@ constructor(
                    }
                },
                media = {
                    with(mediaSection) {
                    with(mediaCarouselElement) {
                        KeyguardMediaCarousel(isShadeLayoutWide = viewModel.isShadeLayoutWide)
                    }
                },
                notifications = {
                    with(notificationSection) {
                    Box(modifier = Modifier.fillMaxHeight()) {
                            AodPromotedNotificationArea()
                        with(aodPromotedNotificationAreaElement) { AodPromotedNotificationArea() }
                        with(notificationsElement) {
                            Notifications(areNotificationsVisible = true, burnInParams = null)
                        }
                    }
                },
                lockIcon = { with(lockSection) { LockIcon() } },
                lockIcon = { with(lockElement) { LockIcon() } },
                startShortcut = {
                    with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
                    with(shortcutElement) { Shortcut(isStart = true, applyPadding = false) }
                },
                ambientIndication = {
                    if (ambientIndicationSectionOptional.isPresent) {
                        with(ambientIndicationSectionOptional.get()) {
                    if (ambientIndicationElementOptional.isPresent) {
                        with(ambientIndicationElementOptional.get()) {
                            AmbientIndication(modifier = Modifier.fillMaxWidth())
                        }
                    }
                },
                bottomIndication = {
                    with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
                    with(indicationAreaElement) {
                        IndicationArea(modifier = Modifier.fillMaxWidth())
                    }
                },
                endShortcut = {
                    with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
                    with(shortcutElement) { Shortcut(isStart = false, applyPadding = false) }
                },
                settingsMenu = {
                    with(settingsMenuSection) { SettingsMenu(onPlaced = onSettingsMenuPlaced) }
                    with(settingsMenuElement) { SettingsMenu(onPlaced = onSettingsMenuPlaced) }
                },
            )
        }
+3 −3
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 * 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.
@@ -14,13 +14,13 @@
 * limitations under the License.
 */

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

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.ContentScope

/** Defines interface for classes that can render the ambient indication area. */
interface AmbientIndicationSection {
interface AmbientIndicationElement {
    @Composable fun ContentScope.AmbientIndication(modifier: Modifier)
}
+108 −0
Original line number Diff line number Diff line
@@ -14,16 +14,12 @@
 * limitations under the License.
 */

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

import android.view.ViewGroup
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -34,112 +30,41 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.ui.SystemBarUtilsState
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.launch

@SysUISingleton
class NotificationSection
class AodNotificationIconsElement
@Inject
constructor(
    private val stackScrollView: Lazy<NotificationScrollView>,
    private val viewModelFactory: NotificationsPlaceholderViewModel.Factory,
    private val aodBurnInViewModel: AodBurnInViewModel,
    sharedNotificationContainer: SharedNotificationContainer,
    sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
    stackScrollLayout: NotificationStackScrollLayout,
    sharedNotificationContainerBinder: SharedNotificationContainerBinder,
    private val keyguardRootViewModel: KeyguardRootViewModel,
    @ShadeDisplayAware private val configurationState: ConfigurationState,
    private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
    private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
    private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
    private val aodPromotedNotificationViewModelFactory: AODPromotedNotificationViewModel.Factory,
    private val systemBarUtilsState: SystemBarUtilsState,
    private val keyguardClockViewModel: KeyguardClockViewModel,
) {

    init {
        // This scene container section moves the NSSL to the SharedNotificationContainer.
        // This also requires that SharedNotificationContainer gets moved to the
        // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container,
        // NSSL is moved into this container by the NotificationStackScrollLayoutSection.
        // Ensure stackScrollLayout is a child of sharedNotificationContainer.

        if (stackScrollLayout.parent != sharedNotificationContainer) {
            (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
            sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
        }

        sharedNotificationContainerBinder.bind(
            sharedNotificationContainer,
            sharedNotificationContainerViewModel,
        )
    }

    @Composable
    fun AodPromotedNotificationArea(modifier: Modifier = Modifier) {
        if (!PromotedNotificationUi.isEnabled) {
            return
        }

        val isVisible by
            keyguardRootViewModel.isAodPromotedNotifVisible.collectAsStateWithLifecycle()
        val transitionState = remember { MutableTransitionState(isVisible.value) }
        LaunchedEffect(key1 = isVisible, key2 = transitionState.isIdle) {
            transitionState.targetState = isVisible.value
            if (isVisible.isAnimating && transitionState.isIdle) {
                isVisible.stopAnimating()
            }
        }
        val burnIn = rememberBurnIn(keyguardClockViewModel)

        AnimatedVisibility(
            visibleState = transitionState,
            enter = if (isVisible.isAnimating) fadeIn() else EnterTransition.None,
            exit = if (isVisible.isAnimating) fadeOut() else ExitTransition.None,
            modifier = modifier.burnInAware(aodBurnInViewModel, burnIn.parameters),
        ) {
            AODPromotedNotification(
                aodPromotedNotificationViewModelFactory,
                modifier = Modifier.sysuiResTag("aod_promoted_notification_frame"),
            )
        }
    }

    @Composable
    fun AodNotificationIcons(modifier: Modifier = Modifier) {
        val isVisible by
@@ -180,40 +105,4 @@ constructor(
            )
        }
    }

    @Composable
    fun ContentScope.HeadsUpNotifications() {
        SnoozeableHeadsUpNotificationSpace(
            stackScrollView = stackScrollView.get(),
            viewModel = rememberViewModel("HeadsUpNotifications") { viewModelFactory.create() },
        )
    }

    /**
     * @param burnInParams params to make this view adaptive to burn-in, `null` to disable burn-in
     *   adjustment
     */
    @Composable
    fun ContentScope.Notifications(
        areNotificationsVisible: Boolean,
        burnInParams: BurnInParameters?,
        modifier: Modifier = Modifier,
    ) {
        if (!areNotificationsVisible) {
            return
        }

        ConstrainedNotificationStack(
            stackScrollView = stackScrollView.get(),
            viewModel = rememberViewModel("Notifications") { viewModelFactory.create() },
            modifier =
                modifier.fillMaxWidth().let {
                    if (burnInParams == null) {
                        it
                    } else {
                        it.burnInAware(viewModel = aodBurnInViewModel, params = burnInParams)
                    }
                },
        )
    }
}
Loading