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

Commit 8b387a38 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[flexiglass] Adds burn-in to smartspace section elements.

This CL has several necessary changes:
1. Extracts burn-in and alpha from KeyguardRootViewModel and onto
   AodBurnInViewModel and AodAlphaViewModel, respectively. This is
   necessary because the new blueprint/section implementation doesn't
   use KeyguardRootView by design (using an @Composable hierarchy
   instead) which means that it cannot use KeyguardRootViewBinder to
   apply the alpha, scale, and translation on the necessary smartspace,
   date, and weather elements (nor can it do so for clock elements -
   something that this CL doesn't do but does enable).
2. Changes the way the old view-binder (KeyguardRootViewBinder) passes
   UI-sourced parameters (like the clock provider, inset, and status
   view position) into the view-model (KeyguardRootViewModel) to be
   passed in when querying the flows that require it, instead of setting
   them as fields on the view-model. This is cleaner but also opens up
   the ability to do the same from Jetpack Compose, where side-effects
   such as this don't necessarily trigger another composition pass.
3. Implements a way to pipe the positioning of the top of the clock
   section and the top of the smartspace section back up to the
   blueprint that owns them and has that blueprint then create the
   burn-in parameters that can be used for (2) in the Compose-based
   implementation. This can be done using the @Composable fun
   burnIn(...) function that then produces an instance of BurnInState
   with proper callbacks for setting the clock controller and reporting
   the tops of the two sections.
4. Implements a simple modifier (fun Modifier.burnInAware) which is then
   used by SmartspaceSection (and can be used by other sections in the
   future) allowing the section to selectively apply the anti-burn-in
   alpha, scale, and translation to selected child composables that need
   that treatment in AOD.
5. Finally, there are some test cleanups: the alpha and burn-in related
   test cases were moved out of KeyguardRootViewModelTest and into the new test classes for the new, smaller view-models which are deviceless-supporting. The KeyguardRootViewModelTest was also moved to make it deviceless-supporting.

Bug: 316211368
Test: See included test changes
Test: manually verified in Flexiglass and outside of Flexiglass
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Change-Id: I1b4affbaeacf7205ee573f5d45df1f8bbcd8f241
parent c61f0302
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.qualifiers.KeyguardRootView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.notifications.ui.composable.NotificationStack
import com.android.systemui.res.R
@@ -47,8 +48,9 @@ import javax.inject.Inject
class ViewBasedLockscreenContent
@Inject
constructor(
    private val viewModel: LockscreenSceneViewModel,
    private val lockscreenSceneViewModel: LockscreenSceneViewModel,
    @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
    private val keyguardRootViewModel: KeyguardRootViewModel,
) {
    @Composable
    fun SceneScope.Content(
@@ -59,7 +61,7 @@ constructor(
        }

        LockscreenLongPress(
            viewModel = viewModel.longPress,
            viewModel = lockscreenSceneViewModel.longPress,
            modifier = modifier,
        ) { onSettingsMenuPlaced ->
            AndroidView(
@@ -74,7 +76,7 @@ constructor(
            )

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

            Layout(
                modifier =
@@ -92,7 +94,7 @@ constructor(
                    },
                content = {
                    NotificationStack(
                        viewModel = viewModel.notifications,
                        viewModel = lockscreenSceneViewModel.notifications,
                        isScrimVisible = false,
                    )
                }
+94 −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.layout.WindowInsets
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.union
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalDensity
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.plugins.clocks.ClockController
import kotlin.math.min
import kotlin.math.roundToInt

/** Produces a [BurnInState] that can be used to query the `LockscreenBurnInViewModel` flows. */
@Composable
fun rememberBurnIn(
    clockInteractor: KeyguardClockInteractor,
): BurnInState {
    val clock by clockInteractor.currentClock.collectAsState()

    val (smartspaceTop, onSmartspaceTopChanged) = remember { mutableStateOf<Float?>(null) }
    val (smallClockTop, onSmallClockTopChanged) = remember { mutableStateOf<Float?>(null) }

    val topmostTop =
        when {
            smartspaceTop != null && smallClockTop != null -> min(smartspaceTop, smallClockTop)
            smartspaceTop != null -> smartspaceTop
            smallClockTop != null -> smallClockTop
            else -> 0f
        }.roundToInt()

    val params = rememberBurnInParameters(clock, topmostTop)

    return remember(params, onSmartspaceTopChanged, onSmallClockTopChanged) {
        BurnInState(
            parameters = params,
            onSmartspaceTopChanged = onSmartspaceTopChanged,
            onSmallClockTopChanged = onSmallClockTopChanged,
        )
    }
}

@Composable
private fun rememberBurnInParameters(
    clock: ClockController?,
    topmostTop: Int,
): BurnInParameters {
    val density = LocalDensity.current
    val topInset = WindowInsets.systemBars.union(WindowInsets.displayCutout).getTop(density)

    return remember(clock, topInset, topmostTop) {
        BurnInParameters(
            clockControllerProvider = { clock },
            topInset = topInset,
            statusViewTop = topmostTop,
        )
    }
}

data class BurnInState(
    /** Parameters for use with the `LockscreenBurnInViewModel. */
    val parameters: BurnInParameters,
    /**
     * Callback to invoke when the top coordinate of the smartspace element is updated, pass `null`
     * when the element is not shown.
     */
    val onSmartspaceTopChanged: (Float?) -> Unit,
    /**
     * Callback to invoke when the top coordinate of the small clock element is updated, pass `null`
     * when the element is not shown.
     */
    val onSmallClockTopChanged: (Float?) -> Unit,
)
+16 −2
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -55,6 +56,7 @@ constructor(
    private val ambientIndicationSection: AmbientIndicationSection,
    private val bottomAreaSection: BottomAreaSection,
    private val settingsMenuSection: SettingsMenuSection,
    private val clockInteractor: KeyguardClockInteractor,
) : LockscreenSceneBlueprint {

    override val id: String = "default"
@@ -62,6 +64,7 @@ constructor(
    @Composable
    override fun SceneScope.Content(modifier: Modifier) {
        val isUdfpsVisible = viewModel.isUdfpsVisible
        val burnIn = rememberBurnIn(clockInteractor)

        LockscreenLongPress(
            viewModel = viewModel.longPress,
@@ -74,8 +77,19 @@ constructor(
                        modifier = Modifier.fillMaxWidth(),
                    ) {
                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
                        with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
                        with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
                        with(clockSection) {
                            SmallClock(
                                onTopChanged = burnIn.onSmallClockTopChanged,
                                modifier = Modifier.fillMaxWidth(),
                            )
                        }
                        with(smartSpaceSection) {
                            SmartSpace(
                                burnInParams = burnIn.parameters,
                                onTopChanged = burnIn.onSmartspaceTopChanged,
                                modifier = Modifier.fillMaxWidth(),
                            )
                        }
                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
                        with(notificationSection) {
                            Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+16 −2
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -55,6 +56,7 @@ constructor(
    private val ambientIndicationSection: AmbientIndicationSection,
    private val bottomAreaSection: BottomAreaSection,
    private val settingsMenuSection: SettingsMenuSection,
    private val clockInteractor: KeyguardClockInteractor,
) : LockscreenSceneBlueprint {

    override val id: String = "shortcuts-besides-udfps"
@@ -62,6 +64,7 @@ constructor(
    @Composable
    override fun SceneScope.Content(modifier: Modifier) {
        val isUdfpsVisible = viewModel.isUdfpsVisible
        val burnIn = rememberBurnIn(clockInteractor)

        LockscreenLongPress(
            viewModel = viewModel.longPress,
@@ -74,8 +77,19 @@ constructor(
                        modifier = Modifier.fillMaxWidth(),
                    ) {
                        with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
                        with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
                        with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
                        with(clockSection) {
                            SmallClock(
                                onTopChanged = burnIn.onSmallClockTopChanged,
                                modifier = Modifier.fillMaxWidth(),
                            )
                        }
                        with(smartSpaceSection) {
                            SmartSpace(
                                burnInParams = burnIn.parameters,
                                onTopChanged = burnIn.onSmartspaceTopChanged,
                                modifier = Modifier.fillMaxWidth(),
                            )
                        }
                        with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
                        with(notificationSection) {
                            Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+68 −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.modifier

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onPlaced
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel

/**
 * Modifies the composable to account for anti-burn in translation, alpha, and scaling.
 *
 * Please override [isClock] as `true` if the composable is an element that's part of a clock.
 */
@Composable
fun Modifier.burnInAware(
    viewModel: AodBurnInViewModel,
    params: BurnInParameters,
    isClock: Boolean = false,
): Modifier {
    val translationX by viewModel.translationX(params).collectAsState(initial = 0f)
    val translationY by viewModel.translationY(params).collectAsState(initial = 0f)
    val alpha by viewModel.alpha.collectAsState(initial = 1f)
    val scaleViewModel by viewModel.scale(params).collectAsState(initial = BurnInScaleViewModel())

    return this.graphicsLayer {
        val scale =
            when {
                scaleViewModel.scaleClockOnly && isClock -> scaleViewModel.scale
                !scaleViewModel.scaleClockOnly -> scaleViewModel.scale
                else -> 1f
            }

        this.translationX = translationX
        this.translationY = translationY
        this.alpha = alpha
        this.scaleX = scale
        this.scaleY = scale
    }
}

/** Reports the "top" coordinate of the modified composable to the given [consumer]. */
@Composable
fun Modifier.onTopPlacementChanged(
    consumer: (Float) -> Unit,
): Modifier {
    return onPlaced { coordinates -> consumer(coordinates.boundsInWindow().top) }
}
Loading