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

Commit f02d5d44 authored by Ale Nijamkin's avatar Ale Nijamkin Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Introduces use of alignment lines." into main

parents fa5f6c18 2c1cf99c
Loading
Loading
Loading
Loading
+81 −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.ui.layout.HorizontalAlignmentLine
import androidx.compose.ui.layout.VerticalAlignmentLine
import kotlin.math.max
import kotlin.math.min

/**
 * Encapsulates all blueprint alignment lines.
 *
 * These can be used to communicate alignment lines emitted by elements that the blueprint should
 * consume and use to know how to constrain and/or place other elements in that blueprint.
 *
 * For more information, please see
 * [the official documentation](https://developer.android.com/jetpack/compose/layouts/alignment-lines).
 */
object BlueprintAlignmentLines {

    /**
     * Encapsulates alignment lines produced by the lock icon element.
     *
     * Because the lock icon is also the same element as the under-display fingerprint sensor
     * (UDFPS), blueprints should use its alignment lines to make sure that other elements on screen
     * do not overlap with the lock icon.
     */
    object LockIcon {

        /** The left edge of the lock icon. */
        val Left =
            VerticalAlignmentLine(
                merger = { old, new ->
                    // When two left alignment line values are provided, choose the leftmost one:
                    min(old, new)
                },
            )

        /** The top edge of the lock icon. */
        val Top =
            HorizontalAlignmentLine(
                merger = { old, new ->
                    // When two top alignment line values are provided, choose the topmost one:
                    min(old, new)
                },
            )

        /** The right edge of the lock icon. */
        val Right =
            VerticalAlignmentLine(
                merger = { old, new ->
                    // When two right alignment line values are provided, choose the rightmost one:
                    max(old, new)
                },
            )

        /** The bottom edge of the lock icon. */
        val Bottom =
            HorizontalAlignmentLine(
                merger = { old, new ->
                    // When two bottom alignment line values are provided, choose the bottommost
                    // one:
                    max(old, new)
                },
            )
    }
}
+91 −39
Original line number Diff line number Diff line
@@ -16,18 +16,13 @@

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

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
import com.android.compose.modifiers.width
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.ClockSection
@@ -62,15 +57,13 @@ constructor(

    @Composable
    override fun SceneScope.Content(modifier: Modifier) {
        val context = LocalContext.current
        val lockIconBounds = lockSection.lockIconBounds(context)
        val isUdfpsVisible = viewModel.isUdfpsVisible

        Box(
            modifier = modifier,
        ) {
        Layout(
            content = {
                // Constrained to above the lock icon.
                Column(
                modifier = Modifier.fillMaxWidth().height { lockIconBounds.top },
                    modifier = Modifier.fillMaxWidth(),
                ) {
                    with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
                    with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
@@ -86,23 +79,82 @@ constructor(
                    }
                }

            with(lockSection) {
                LockIcon(
                    modifier =
                        Modifier.width { lockIconBounds.width() }
                            .height { lockIconBounds.height() }
                            .offset { IntOffset(lockIconBounds.left, lockIconBounds.top) }
                )
            }
                with(lockSection) { LockIcon() }

            Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) {
                // Aligned to bottom and constrained to below the lock icon.
                Column(modifier = Modifier.fillMaxWidth()) {
                    if (isUdfpsVisible) {
                        with(ambientIndicationSection) {
                            AmbientIndication(modifier = Modifier.fillMaxWidth())
                        }
                    }

                with(bottomAreaSection) { BottomArea(modifier = Modifier.fillMaxWidth()) }
                    with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
                }

                // Aligned to bottom and NOT constrained by the lock icon.
                with(bottomAreaSection) {
                    Shortcut(isStart = true, applyPadding = true)
                    Shortcut(isStart = false, applyPadding = true)
                }
            },
            modifier = modifier,
        ) { measurables, constraints ->
            check(measurables.size == 5)
            val (
                aboveLockIconMeasurable,
                lockIconMeasurable,
                belowLockIconMeasurable,
                startShortcutMeasurable,
                endShortcutMeasurable,
            ) = measurables

            val noMinConstraints =
                constraints.copy(
                    minWidth = 0,
                    minHeight = 0,
                )
            val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
            val lockIconBounds =
                IntRect(
                    left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
                    top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
                    right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
                    bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
                )

            val aboveLockIconPlaceable =
                aboveLockIconMeasurable.measure(
                    noMinConstraints.copy(maxHeight = lockIconBounds.top)
                )
            val belowLockIconPlaceable =
                belowLockIconMeasurable.measure(
                    noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
                )
            val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
            val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)

            layout(constraints.maxWidth, constraints.maxHeight) {
                aboveLockIconPlaceable.place(
                    x = 0,
                    y = 0,
                )
                lockIconPlaceable.place(
                    x = lockIconBounds.left,
                    y = lockIconBounds.top,
                )
                belowLockIconPlaceable.place(
                    x = 0,
                    y = constraints.maxHeight - belowLockIconPlaceable.height,
                )
                startShortcutPleaceable.place(
                    x = 0,
                    y = constraints.maxHeight - startShortcutPleaceable.height,
                )
                endShortcutPleaceable.place(
                    x = constraints.maxWidth - endShortcutPleaceable.width,
                    y = constraints.maxHeight - endShortcutPleaceable.height,
                )
            }
        }
    }
+92 −94
Original line number Diff line number Diff line
@@ -16,24 +16,13 @@

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

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
import com.android.compose.modifiers.width
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.ClockSection
@@ -42,12 +31,10 @@ import com.android.systemui.keyguard.ui.composable.section.NotificationSection
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.LockscreenContentViewModel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
import javax.inject.Inject
import kotlin.math.roundToInt

/**
 * Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -70,15 +57,13 @@ constructor(

    @Composable
    override fun SceneScope.Content(modifier: Modifier) {
        val context = LocalContext.current
        val lockIconBounds = lockSection.lockIconBounds(context)
        val isUdfpsVisible = viewModel.isUdfpsVisible

        Box(
            modifier = modifier,
        ) {
        Layout(
            content = {
                // Constrained to above the lock icon.
                Column(
                modifier = Modifier.fillMaxWidth().height { lockIconBounds.top },
                    modifier = Modifier.fillMaxWidth(),
                ) {
                    with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
                    with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
@@ -94,72 +79,85 @@ constructor(
                    }
                }

            val shortcutSizePx =
                with(LocalDensity.current) { bottomAreaSection.shortcutSizeDp().toSize() }

            Row(
                verticalAlignment = Alignment.CenterVertically,
                modifier =
                    Modifier.fillMaxWidth().offset {
                        val rowTop =
                            if (shortcutSizePx.height > lockIconBounds.height()) {
                                (lockIconBounds.top -
                                        (shortcutSizePx.height + lockIconBounds.height()) / 2)
                                    .roundToInt()
                            } else {
                                lockIconBounds.top
                            }

                        IntOffset(0, rowTop)
                    },
            ) {
                Spacer(Modifier.weight(1f))

                with(bottomAreaSection) { Shortcut(isStart = true) }

                Spacer(Modifier.weight(1f))

                with(lockSection) {
                    LockIcon(
                        modifier =
                            Modifier.width { lockIconBounds.width() }
                                .height { lockIconBounds.height() }
                    )
                }

                Spacer(Modifier.weight(1f))
                // Constrained to the left of the lock icon (in left-to-right layouts).
                with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }

                with(bottomAreaSection) { Shortcut(isStart = false) }
                with(lockSection) { LockIcon() }

                Spacer(Modifier.weight(1f))
            }
                // Constrained to the right of the lock icon (in left-to-right layouts).
                with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }

            Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) {
                // Aligned to bottom and constrained to below the lock icon.
                Column(modifier = Modifier.fillMaxWidth()) {
                    if (isUdfpsVisible) {
                        with(ambientIndicationSection) {
                            AmbientIndication(modifier = Modifier.fillMaxWidth())
                        }
                    }

                with(bottomAreaSection) {
                    IndicationArea(
                        modifier =
                            Modifier.fillMaxWidth()
                                .padding(
                                    horizontal =
                                        dimensionResource(
                                            R.dimen.keyguard_affordance_horizontal_offset
                    with(bottomAreaSection) { IndicationArea(modifier = Modifier.fillMaxWidth()) }
                }
            },
            modifier = modifier,
        ) { measurables, constraints ->
            check(measurables.size == 5)
            val (
                aboveLockIconMeasurable,
                startSideShortcutMeasurable,
                lockIconMeasurable,
                endSideShortcutMeasurable,
                belowLockIconMeasurable,
            ) = measurables

            val noMinConstraints =
                constraints.copy(
                    minWidth = 0,
                    minHeight = 0,
                )

            val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
            val lockIconBounds =
                IntRect(
                    left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
                    top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
                    right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
                    bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
                )

            val aboveLockIconPlaceable =
                aboveLockIconMeasurable.measure(
                    noMinConstraints.copy(maxHeight = lockIconBounds.top)
                )
                                .padding(
                                    bottom =
                                        dimensionResource(
                                            R.dimen.keyguard_affordance_vertical_offset
            val startSideShortcutPlaceable = startSideShortcutMeasurable.measure(noMinConstraints)
            val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
            val belowLockIconPlaceable =
                belowLockIconMeasurable.measure(
                    noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
                )

            layout(constraints.maxWidth, constraints.maxHeight) {
                aboveLockIconPlaceable.place(
                    x = 0,
                    y = 0,
                )
                                .heightIn(min = shortcutSizeDp().height),
                startSideShortcutPlaceable.placeRelative(
                    x = lockIconBounds.left / 2 - startSideShortcutPlaceable.width / 2,
                    y = lockIconBounds.center.y - startSideShortcutPlaceable.height / 2,
                )
                lockIconPlaceable.place(
                    x = lockIconBounds.left,
                    y = lockIconBounds.top,
                )
                endSideShortcutPlaceable.placeRelative(
                    x =
                        lockIconBounds.right + (constraints.maxWidth - lockIconBounds.right) / 2 -
                            endSideShortcutPlaceable.width / 2,
                    y = lockIconBounds.center.y - endSideShortcutPlaceable.height / 2,
                )
                belowLockIconPlaceable.place(
                    x = 0,
                    y = constraints.maxHeight - belowLockIconPlaceable.height,
                )
                }
            }
        }
    }
+23 −31
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.systemui.keyguard.ui.composable.section
import android.view.View
import android.widget.ImageView
import androidx.annotation.IdRes
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -58,38 +57,17 @@ constructor(
    private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
    private val keyguardRootViewModel: KeyguardRootViewModel,
) {
    @Composable
    fun SceneScope.BottomArea(
        modifier: Modifier = Modifier,
    ) {
        Row(
            modifier =
                modifier
                    .padding(
                        horizontal =
                            dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
                    )
                    .padding(
                        bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset)
                    ),
        ) {
            Shortcut(
                isStart = true,
            )

            IndicationArea(
                modifier = Modifier.weight(1f),
            )

            Shortcut(
                isStart = false,
            )
        }
    }

    /**
     * Renders a single lockscreen shortcut.
     *
     * @param isStart Whether the shortcut goes on the left (in left-to-right locales).
     * @param applyPadding Whether to apply padding around the shortcut, this is needed if the
     *   shortcut is placed along the edges of the display.
     */
    @Composable
    fun SceneScope.Shortcut(
        isStart: Boolean,
        applyPadding: Boolean,
        modifier: Modifier = Modifier,
    ) {
        MovableElement(
@@ -103,6 +81,12 @@ constructor(
                falsingManager = falsingManager,
                vibratorHelper = vibratorHelper,
                indicationController = indicationController,
                modifier =
                    if (applyPadding) {
                        Modifier.shortcutPadding()
                    } else {
                        Modifier
                    }
            )
        }
    }
@@ -113,7 +97,7 @@ constructor(
    ) {
        MovableElement(
            key = IndicationAreaElementKey,
            modifier = modifier,
            modifier = modifier.shortcutPadding(),
        ) {
            IndicationArea(
                indicationAreaViewModel = indicationAreaViewModel,
@@ -218,6 +202,14 @@ constructor(
            modifier = modifier.fillMaxWidth(),
        )
    }

    @Composable
    private fun Modifier.shortcutPadding(): Modifier {
        return this.padding(
                horizontal = dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
            )
            .padding(bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset))
    }
}

private val StartButtonElementKey = ElementKey("StartButton")
+59 −33
Original line number Diff line number Diff line
@@ -17,8 +17,6 @@
package com.android.systemui.keyguard.ui.composable.section

import android.content.Context
import android.graphics.Point
import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.compose.foundation.background
@@ -28,11 +26,17 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.biometrics.AuthController
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines
import com.android.systemui.res.R
import javax.inject.Inject

@@ -49,8 +53,33 @@ constructor(
            key = LockIconElementKey,
            modifier = modifier,
        ) {
            val context = LocalContext.current
            Box(
                modifier = Modifier.background(Color.Red),
                modifier =
                    Modifier.background(Color.Red).layout { measurable, _ ->
                        val lockIconBounds = lockIconBounds(context)
                        val placeable =
                            measurable.measure(
                                Constraints.fixed(
                                    width = lockIconBounds.width,
                                    height = lockIconBounds.height,
                                )
                            )
                        layout(
                            width = placeable.width,
                            height = placeable.height,
                            alignmentLines =
                                mapOf(
                                    BlueprintAlignmentLines.LockIcon.Left to lockIconBounds.left,
                                    BlueprintAlignmentLines.LockIcon.Top to lockIconBounds.top,
                                    BlueprintAlignmentLines.LockIcon.Right to lockIconBounds.right,
                                    BlueprintAlignmentLines.LockIcon.Bottom to
                                        lockIconBounds.bottom,
                                ),
                        ) {
                            placeable.place(0, 0)
                        }
                    },
            ) {
                Text(
                    text = "TODO(b/316211368): Lock",
@@ -67,9 +96,9 @@ constructor(
     * On devices that support UDFPS (under-display fingerprint sensor), the bounds of the icon are
     * the same as the bounds of the sensor.
     */
    fun lockIconBounds(
    private fun lockIconBounds(
        context: Context,
    ): Rect {
    ): IntRect {
        val windowViewBounds = windowManager.currentWindowMetrics.bounds
        var widthPx = windowViewBounds.right.toFloat()
        if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) {
@@ -85,36 +114,33 @@ constructor(
        val lockIconRadiusPx = (defaultDensity * 36).toInt()

        val udfpsLocation = authController.udfpsLocation
        return if (authController.isUdfpsSupported && udfpsLocation != null) {
            centerLockIcon(udfpsLocation, authController.udfpsRadius)
        val (center, radius) =
            if (authController.isUdfpsSupported && udfpsLocation != null) {
                Pair(
                    IntOffset(
                        x = udfpsLocation.x,
                        y = udfpsLocation.y,
                    ),
                    authController.udfpsRadius.toInt(),
                )
            } else {
                val scaleFactor = authController.scaleFactor
                val bottomPaddingPx =
                    context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
                val heightPx = windowViewBounds.bottom.toFloat()

            centerLockIcon(
                Point(
                    (widthPx / 2).toInt(),
                    (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
                Pair(
                    IntOffset(
                        x = (widthPx / 2).toInt(),
                        y =
                            (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor))
                                .toInt(),
                    ),
                lockIconRadiusPx * scaleFactor
                    (lockIconRadiusPx * scaleFactor).toInt(),
                )
            }
    }

    private fun centerLockIcon(
        center: Point,
        radius: Float,
    ): Rect {
        return Rect().apply {
            set(
                center.x - radius.toInt(),
                center.y - radius.toInt(),
                center.x + radius.toInt(),
                center.y + radius.toInt(),
            )
        }
        return IntRect(center, radius)
    }
}