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

Commit dd4a629a authored by Matt Pietal's avatar Matt Pietal Committed by Android (Google) Code Review
Browse files

Merge "Add Occluded Scene" into main

parents d6ca0bbe 133233d8
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 * Copyright 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,19 +14,16 @@
 * limitations under the License.
 */

package com.android.systemui.scene.domain.interactor
package com.android.systemui.scene

import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.occluded.ui.composable.OccludedScene
import com.android.systemui.scene.ui.composable.Scene
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet

val Kosmos.sceneContainerOcclusionInteractor by Fixture {
    SceneContainerOcclusionInteractor(
        applicationScope = applicationCoroutineScope,
        keyguardOcclusionInteractor = keyguardOcclusionInteractor,
        sceneInteractor = sceneInteractor,
        keyguardTransitionInteractor = keyguardTransitionInteractor,
    )
@Module
interface OccludedSceneModule {

    @Binds @IntoSet fun occludedScene(scene: OccludedScene): Scene
}
+0 −10
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.composable

import android.view.View
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
@@ -84,15 +83,6 @@ class LockscreenContent(
        // Ensure clock events are connected. This is a no-op if they are already registered.
        clockInteractor.clockEventController.registerListeners()

        if (!viewModel.isContentVisible) {
            // If the content isn't supposed to be visible, show a large empty box as it's needed
            // for scene transition animations (can't just skip rendering everything or shared
            // elements won't have correct final/initial bounds from animating in and out of the
            // lockscreen scene).
            Box(modifier)
            return
        }

        DisposableEffect(view) {
            val handle = clockInteractor.clockEventController.bind(view)
            onDispose { handle.dispose() }
+124 −0
Original line number Diff line number Diff line
/*
 * 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.
 * 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.occluded.ui.composable

import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toAndroidRectF
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateContentFloatAsState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.notifications.ui.composable.headsUpTopInset
import com.android.systemui.qs.shared.ui.QuickSettings
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.scene.ui.viewmodel.OccludedUserActionsViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow

/** The occluded scene shows when a non-dream activity is showing over keyguard */
@SysUISingleton
class OccludedScene
@Inject
constructor(
    private val notificationStackScrollView: Lazy<NotificationScrollView>,
    private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
    private val viewModelFactory: OccludedUserActionsViewModel.Factory,
) : ExclusiveActivatable(), Scene {
    override val key = Scenes.Occluded

    private val actionsViewModel: OccludedUserActionsViewModel by lazy { viewModelFactory.create() }

    override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions

    override val alwaysCompose: Boolean = false

    override suspend fun onActivated(): Nothing {
        actionsViewModel.activate()
    }

    @Composable
    override fun ContentScope.Content(modifier: Modifier) {

        val isIdleAndNotShadeExpanded =
            with(layoutState.transitionState) {
                isIdle(key) &&
                    !isIdle(Overlays.NotificationsShade) &&
                    !isIdle(Overlays.QuickSettingsShade)
            }

        val headsUpInset = with(LocalDensity.current) { headsUpTopInset().toPx() }

        LaunchedEffect(isIdleAndNotShadeExpanded) {
            // Wait for being Idle on this Scene, otherwise LaunchedEffect would fire too soon,
            // and another transition could override the NSSL stack bounds.
            if (isIdleAndNotShadeExpanded) {
                // Reset the stack bounds to avoid caching these values from the previous Scenes,
                // and not to confuse the StackScrollAlgorithm when it displays a HUN over OCCLUDED.
                notificationStackScrollView.get().apply {
                    // use -headsUpInset to allow HUN translation outside bounds for snoozing
                    setStackTop(-headsUpInset)
                }
            }
        }

        animateContentFloatAsState(
            value = QuickSettings.SharedValues.SquishinessValues.OccludedSceneStarting,
            key = QuickSettings.SharedValues.TilesSquishiness,
        )
        Spacer(modifier.fillMaxSize())
        SnoozeableHeadsUpNotificationSpace(
            modifier =
                Modifier.onGloballyPositioned {
                    // Once we are on the non-occluded Lockscreen, the regular stack is not setting
                    // draw bounds anymore, but HUNs can still appear.
                    if (isIdleAndNotShadeExpanded) {
                        notificationStackScrollView
                            .get()
                            .updateDrawBounds(
                                it.boundsInWindow().toAndroidRectF().apply {
                                    // extend bounds to the screen top to avoid cutting off HUN
                                    // transitions
                                    top = 0f
                                }
                            )
                    }
                },
            stackScrollView = notificationStackScrollView.get(),
            viewModel =
                rememberViewModel("OccludedScene") {
                    notificationsPlaceholderViewModelFactory.create()
                },
        )
    }
}
+5 −8
Original line number Diff line number Diff line
@@ -20,9 +20,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toAndroidRectF
import androidx.compose.ui.layout.boundsInWindow
@@ -32,7 +30,6 @@ import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateContentFloatAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
@@ -75,11 +72,11 @@ constructor(
    @Composable
    override fun ContentScope.Content(modifier: Modifier) {

        val isIdleAndNotOccluded by remember {
            derivedStateOf {
                layoutState.transitionState is TransitionState.Idle &&
                    Overlays.NotificationsShade !in layoutState.transitionState.currentOverlays
            }
        val isIdleAndNotOccluded =
            with(layoutState.transitionState) {
                isIdle(key) &&
                    !isIdle(Overlays.NotificationsShade) &&
                    !isIdle(Overlays.QuickSettingsShade)
            }

        val headsUpInset = with(LocalDensity.current) { headsUpTopInset().toPx() }
+2 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import com.android.systemui.scene.ui.composable.transitions.lockscreenToCommunal
import com.android.systemui.scene.ui.composable.transitions.lockscreenToDreamTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToNotificationsShadeTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToOccludedTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsOverlayTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsSceneTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeSceneTransition
@@ -114,6 +115,7 @@ class SceneContainerTransitions : SceneContainerTransitionsBuilder {

            from(Scenes.Lockscreen, to = Scenes.Communal) { lockscreenToCommunalTransition() }
            from(Scenes.Lockscreen, to = Scenes.Dream) { lockscreenToDreamTransition() }
            from(Scenes.Lockscreen, to = Scenes.Occluded) { lockscreenToOccludedTransition() }
            from(
                Scenes.Lockscreen,
                to = Scenes.Shade,
Loading