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

Commit f6598d80 authored by András Kurucz's avatar András Kurucz
Browse files

[Flexiglass] Remove NotificationLockscreenScrim from LockscreenContent

NotificationLockscreenScrim was an opaque full screen box over the
Lockscreen, which had a matching background with the ShadeScene. This
provided a continuity in showing a black/white surface after the
ShadeScene -> LockscreenScene transition has finished, and finalized
this transition by starting a fade-out animation when composing the
LocksceenScene.

This transition broke when the ShadeScene became transparent, and
showing the NotificationLockscreenScrim resulted in a "flash".

This CL removes the "flash" by inverting the fade-out animation to get a
fade-in, and apply it to the Lockscreen content after the Shade ->
Lockscreen transition has finished.

Fixes: 437121774
Test: check the ShadeScene to LockscreenScene transition -> Lockscreen content fades in, the "flash" is gone
Flag: com.android.systemui.scene_container

Change-Id: I7b6581cca6da45ea9655e6acfbb34a7dbb071bbb
parent 96f6c51e
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenBehindScrimViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenFrontScrimViewModel
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLockscreenScrimViewModel
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -62,7 +61,6 @@ interface LockscreenSceneModule {
        @Provides
        fun providesLockscreenContent(
            viewModelFactory: LockscreenContentViewModel.Factory,
            notificationScrimViewModelFactory: NotificationLockscreenScrimViewModel.Factory,
            lockscreenFrontScrimViewModelFactory: LockscreenFrontScrimViewModel.Factory,
            lockscreenBehindScrimViewModelFactory: LockscreenBehindScrimViewModel.Factory,
            blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
@@ -71,7 +69,6 @@ interface LockscreenSceneModule {
        ): LockscreenContent {
            return LockscreenContent(
                viewModelFactory,
                notificationScrimViewModelFactory,
                lockscreenFrontScrimViewModelFactory,
                lockscreenBehindScrimViewModelFactory,
                blueprints,
+59 −9
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.systemui.keyguard.ui.composable

import android.view.View
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -24,6 +26,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalView
@@ -41,9 +44,8 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenFrontScrimViewModel
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.composable.NotificationLockscreenScrim
import com.android.systemui.plugins.keyguard.ui.composable.elements.LockscreenElementKeys
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLockscreenScrimViewModel
import kotlin.math.min

/**
 * Renders the content of the lockscreen.
@@ -53,7 +55,6 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati
 */
class LockscreenContent(
    private val viewModelFactory: LockscreenContentViewModel.Factory,
    private val notificationScrimViewModelFactory: NotificationLockscreenScrimViewModel.Factory,
    private val lockscreenFrontScrimViewModelFactory: LockscreenFrontScrimViewModel.Factory,
    private val lockscreenBehindScrimViewModelFactory: LockscreenBehindScrimViewModel.Factory,
    private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
@@ -78,10 +79,6 @@ class LockscreenContent(
            }

        LaunchedEffect(viewModel.alpha) { lockscreenAlpha = viewModel.alpha }
        val notificationLockscreenScrimViewModel =
            rememberViewModel("LockscreenContent-notificationScrimViewModel") {
                notificationScrimViewModelFactory.create()
            }
        val lockscreenFrontScrimViewModel =
            rememberViewModel("LockscreenContent-frontScrimViewModel") {
                lockscreenFrontScrimViewModelFactory.create()
@@ -91,6 +88,60 @@ class LockscreenContent(
                lockscreenBehindScrimViewModelFactory.create()
            }

        /**
         * Important: Make sure that [LockscreenContentViewModel.shouldContentFadeIn] is checked the
         * first time the Lockscreen scene is composed.
         */
        val useFadeInOnComposition = remember {
            layoutState.currentTransition?.let { currentTransition ->
                viewModel.shouldContentFadeIn(currentTransition)
            } ?: false
        }

        // Alpha for the animation when transitioning from Shade scene to Lockscreen Scene and
        // ending user input, at which point the content fades in, visually completing the
        // transition.
        val contentAlphaAnimatable = remember { Animatable(0f) }
        LaunchedEffect(contentAlphaAnimatable) {
            snapshotFlow { contentAlphaAnimatable.value }
                .collect {
                    // Pipe the content alpha animation progress to the view model, so NSSL can
                    // fade-in the stack in tandem.
                    viewModel.setContentAlphaForLockscreenFadeIn(it)
                }
        }

        LaunchedEffect(
            contentAlphaAnimatable,
            layoutState.currentTransition,
            useFadeInOnComposition,
        ) {
            val currentTransition = layoutState.currentTransition
            when {
                useFadeInOnComposition &&
                    currentTransition != null &&
                    viewModel.shouldContentFadeIn(currentTransition) &&
                    currentTransition.isUserInputOngoing -> {

                    // Keep the content invisible until user lifts their finger.
                    contentAlphaAnimatable.snapTo(0f)
                }

                useFadeInOnComposition &&
                    (currentTransition == null ||
                        (viewModel.shouldContentFadeIn(currentTransition) &&
                            !currentTransition.isUserInputOngoing)) -> {
                    // Animate the content fade in.
                    contentAlphaAnimatable.animateTo(1f, tween())
                }

                else -> {
                    // Disable the content fade in logic.
                    contentAlphaAnimatable.snapTo(1f)
                }
            }
        }

        // Ensure clock events are connected. This is a no-op if they are already registered.
        clockInteractor.clockEventController.registerListeners()

@@ -110,9 +161,8 @@ class LockscreenContent(
                modifier
                    .sysuiResTag("keyguard_root_view")
                    .element(LockscreenElementKeys.Root)
                    .graphicsLayer { alpha = viewModel.alpha },
                    .graphicsLayer { alpha = min(viewModel.alpha, contentAlphaAnimatable.value) },
            )
            NotificationLockscreenScrim(notificationLockscreenScrimViewModel)
            LockscreenFrontScrim(lockscreenFrontScrimViewModel)
        }
    }
+0 −125
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.notifications.ui.composable

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLockscreenScrimViewModel
import kotlinx.coroutines.launch

/**
 * A full-screen notifications scrim that is only visible after transitioning from Shade scene to
 * Lockscreen Scene and ending user input, at which point it fades out, visually completing the
 * transition.
 */
@Composable
fun ContentScope.NotificationLockscreenScrim(
    viewModel: NotificationLockscreenScrimViewModel,
    modifier: Modifier = Modifier,
) {
    val coroutineScope = rememberCoroutineScope()
    val shadeMode = viewModel.shadeMode.collectAsStateWithLifecycle()

    // Important: Make sure that shouldShowScrimFadeOut() is checked the first time the Lockscreen
    // scene is composed.
    val useFadeOutOnComposition =
        remember(shadeMode.value) {
            layoutState.currentTransition?.let { currentTransition ->
                shouldShowScrimFadeOut(currentTransition, shadeMode.value)
            } ?: false
        }

    val alphaAnimatable = remember { Animatable(1f) }

    LaunchedEffect(
        alphaAnimatable,
        layoutState.currentTransition,
        useFadeOutOnComposition,
        shadeMode,
    ) {
        val currentTransition = layoutState.currentTransition
        if (
            useFadeOutOnComposition &&
                currentTransition != null &&
                shouldShowScrimFadeOut(currentTransition, shadeMode.value) &&
                currentTransition.isUserInputOngoing
        ) {
            // keep scrim visible until user lifts their finger.
            viewModel.setAlphaForLockscreenFadeIn(0f)
            alphaAnimatable.snapTo(1f)
        } else if (
            useFadeOutOnComposition &&
                (currentTransition == null ||
                    (shouldShowScrimFadeOut(currentTransition, shadeMode.value) &&
                        !currentTransition.isUserInputOngoing))
        ) {
            // we no longer want to keep the scrim from fading out, so animate the scrim fade-out
            // and pipe the progress to the view model as well, so NSSL can fade-in the stack in
            // tandem.
            viewModel.setAlphaForLockscreenFadeIn(0f)
            coroutineScope.launch {
                snapshotFlow { alphaAnimatable.value }
                    .collect { viewModel.setAlphaForLockscreenFadeIn(1 - it) }
            }
            alphaAnimatable.animateTo(0f, tween())
        } else {
            // disable the scrim fade logic.
            viewModel.setAlphaForLockscreenFadeIn(1f)
            alphaAnimatable.snapTo(0f)
        }
    }

    val isBouncerToLockscreen =
        layoutState.currentTransition?.isTransitioning(
            from = Overlays.Bouncer,
            to = Scenes.Lockscreen,
        ) ?: false

    Box(
        modifier
            .fillMaxSize()
            .element(viewModel.element.key)
            .graphicsLayer { alpha = alphaAnimatable.value }
            .background(viewModel.element.color(isBouncerToLockscreen))
    )
}

private fun shouldShowScrimFadeOut(
    currentTransition: TransitionState.Transition,
    shadeMode: ShadeMode,
): Boolean {
    return shadeMode != ShadeMode.Dual &&
        currentTransition.isInitiatedByUserInput &&
        (currentTransition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) ||
            currentTransition.isTransitioning(from = Overlays.Bouncer, to = Scenes.Lockscreen))
}
+23 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel

import androidx.compose.runtime.getValue
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBypassInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
@@ -25,7 +26,11 @@ import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimati
import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallbackDelegator
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.wallpapers.domain.interactor.WallpaperFocalAreaInteractor
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -46,6 +51,7 @@ constructor(
    private val keyguardTransitionAnimationCallbackDelegator:
        KeyguardTransitionAnimationCallbackDelegator,
    private val wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor,
    private val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
    private val lockscreenAlphaViewModelFactory: LockscreenAlphaViewModel.Factory,
    @Assisted private val keyguardTransitionAnimationCallback: KeyguardTransitionAnimationCallback,
    @Assisted private val viewStateAccessor: ViewStateAccessor,
@@ -60,6 +66,10 @@ constructor(
            source = shadeModeInteractor.isFullWidthShade,
        )

    /** @see ShadeModeInteractor.shadeMode */
    val shadeMode: ShadeMode by
        hydrator.hydratedStateOf(traceName = "shadeMode", source = shadeModeInteractor.shadeMode)

    /** @see DeviceEntryBypassInteractor.isBypassEnabled */
    val isBypassEnabled: Boolean by
        hydrator.hydratedStateOf(
@@ -122,6 +132,19 @@ constructor(
        wallpaperFocalAreaInteractor.setSmartspaceCardBottom(bottom)
    }

    /** Sets the alpha to apply to the NSSL for fade-in on lockscreen */
    fun setContentAlphaForLockscreenFadeIn(alpha: Float) {
        notificationStackAppearanceInteractor.setAlphaForLockscreenFadeIn(alpha)
    }

    /** Should a content reveal animation run for the given transition */
    fun shouldContentFadeIn(currentTransition: TransitionState.Transition): Boolean {
        return shadeMode != ShadeMode.Dual &&
            currentTransition.isInitiatedByUserInput &&
            (currentTransition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) ||
                currentTransition.isTransitioning(from = Overlays.Bouncer, to = Scenes.Lockscreen))
    }

    @AssistedFactory
    interface Factory {
        fun create(
+0 −125
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.statusbar.notification.stack.ui.viewmodel

import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import com.android.app.tracing.coroutines.coroutineScopeTraced
import com.android.compose.animation.scene.ElementKey
import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.util.kotlin.ActivatableFlowDumper
import com.android.systemui.util.kotlin.ActivatableFlowDumperImpl
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class NotificationLockscreenScrimViewModel
@AssistedInject
constructor(
    dumpManager: DumpManager,
    shadeModeInteractor: ShadeModeInteractor,
    private val stackAppearanceInteractor: NotificationStackAppearanceInteractor,
) :
    ActivatableFlowDumper by ActivatableFlowDumperImpl(dumpManager, "NotificationScrollViewModel"),
    ExclusiveActivatable() {

    private val hydrator = Hydrator("NotificationLockscreenScrimViewModel.hydrator")

    val shadeMode: StateFlow<ShadeMode> = shadeModeInteractor.shadeMode

    /** The [ElementKey] to use for the scrim. */
    val element: ElementViewModel by
        hydrator.hydratedStateOf(
            traceName = "elementKey",
            initialValue = element(shadeMode.value),
            source = shadeMode.map { element(it) },
        )

    /** Sets the alpha to apply to the NSSL for fade-in on lockscreen */
    fun setAlphaForLockscreenFadeIn(alpha: Float) {
        stackAppearanceInteractor.setAlphaForLockscreenFadeIn(alpha)
    }

    override suspend fun onActivated(): Nothing {
        coroutineScopeTraced("NotificationLockscreenScrimViewModel") {
            launch { activateFlowDumper() }
            launch { hydrator.activate() }
            awaitCancellation()
        }
    }

    private fun element(shadeMode: ShadeMode): ElementViewModel {
        return if (shadeMode == ShadeMode.Single) {
            ElementViewModel(
                key = Notifications.Elements.NotificationScrim,
                color = { SingleShadeBackground },
            )
        } else {
            ElementViewModel(
                key = Shade.Elements.BackgroundScrim,
                color = { isBouncerToLockscreen ->
                    if (isBouncerToLockscreen) {
                        SplitShadeBouncerToLockscreenBackground
                    } else {
                        SplitShadeDefaultBackground
                    }
                },
            )
        }
    }

    /** Models the UI state of the scrim. */
    data class ElementViewModel(
        /** The [ElementKey] to use with an `element` modifier. */
        val key: ElementKey,
        /** A function that returns the color to use within a `background` modifier. */
        val color: @Composable (isBouncerToLockscreen: Boolean) -> Color,
    )

    @AssistedFactory
    interface Factory {
        fun create(): NotificationLockscreenScrimViewModel
    }

    companion object {
        private val SingleShadeBackground: Color
            @Composable @ReadOnlyComposable get() = MaterialTheme.colorScheme.surface

        private val SplitShadeBouncerToLockscreenBackground: Color
            @Composable @ReadOnlyComposable get() = MaterialTheme.colorScheme.surface

        private val SplitShadeDefaultBackground: Color
            @Composable
            @ReadOnlyComposable
            get() = colorResource(R.color.shade_scrim_background_dark)
    }
}
Loading