[flexiglass] Introduces use of alignment lines.

Alignment lines are used to communicate the position of the lock icon
and for new custom layout logic in both the default and shortcuts-beside-UDFPS
blueprints to avoid placing other elements on top of the lock icon.

This acts as a proof of concept, proving that we could also use
alignment lines from the clock section to communicate the same such that
blueprints can place elements above, below, or around various clock
elements (some clocks, like the weather clock, use multiple clock
elements and need other lockscreen elements to not overlap them).

Bug: 316211368
Test: manually verified on both blueprints
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Change-Id: Ic014e30e8b354b1952992f67f091d4da20c61923
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt
new file mode 100644
index 0000000..efa8cc7
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BlueprintAlignmentLines.kt
@@ -0,0 +1,81 @@
+/*
+ * 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)
+                },
+            )
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index fc1df84..7314453 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -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,47 +57,104 @@
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        val context = LocalContext.current
-        val lockIconBounds = lockSection.lockIconBounds(context)
         val isUdfpsVisible = viewModel.isUdfpsVisible
 
-        Box(
+        Layout(
+            content = {
+                // Constrained to above the lock icon.
+                Column(
+                    modifier = Modifier.fillMaxWidth(),
+                ) {
+                    with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+                    with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+                    with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+                    with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+                    with(notificationSection) {
+                        Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+                    }
+                    if (!isUdfpsVisible) {
+                        with(ambientIndicationSection) {
+                            AmbientIndication(modifier = Modifier.fillMaxWidth())
+                        }
+                    }
+                }
+
+                with(lockSection) { LockIcon() }
+
+                // 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()) }
+                }
+
+                // Aligned to bottom and NOT constrained by the lock icon.
+                with(bottomAreaSection) {
+                    Shortcut(isStart = true, applyPadding = true)
+                    Shortcut(isStart = false, applyPadding = true)
+                }
+            },
             modifier = modifier,
-        ) {
-            Column(
-                modifier = Modifier.fillMaxWidth().height { lockIconBounds.top },
-            ) {
-                with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
-                with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
-                with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                with(notificationSection) {
-                    Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
-                }
-                if (!isUdfpsVisible) {
-                    with(ambientIndicationSection) {
-                        AmbientIndication(modifier = Modifier.fillMaxWidth())
-                    }
-                }
-            }
+        ) { measurables, constraints ->
+            check(measurables.size == 5)
+            val (
+                aboveLockIconMeasurable,
+                lockIconMeasurable,
+                belowLockIconMeasurable,
+                startShortcutMeasurable,
+                endShortcutMeasurable,
+            ) = measurables
 
-            with(lockSection) {
-                LockIcon(
-                    modifier =
-                        Modifier.width { lockIconBounds.width() }
-                            .height { lockIconBounds.height() }
-                            .offset { IntOffset(lockIconBounds.left, lockIconBounds.top) }
+            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],
+                )
 
-            Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) {
-                if (isUdfpsVisible) {
-                    with(ambientIndicationSection) {
-                        AmbientIndication(modifier = Modifier.fillMaxWidth())
-                    }
-                }
+            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)
 
-                with(bottomAreaSection) { BottomArea(modifier = Modifier.fillMaxWidth()) }
+            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,
+                )
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index fa913f1..4c119c7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -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.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,96 +57,107 @@
 
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
-        val context = LocalContext.current
-        val lockIconBounds = lockSection.lockIconBounds(context)
         val isUdfpsVisible = viewModel.isUdfpsVisible
 
-        Box(
+        Layout(
+            content = {
+                // Constrained to above the lock icon.
+                Column(
+                    modifier = Modifier.fillMaxWidth(),
+                ) {
+                    with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+                    with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
+                    with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+                    with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+                    with(notificationSection) {
+                        Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
+                    }
+                    if (!isUdfpsVisible) {
+                        with(ambientIndicationSection) {
+                            AmbientIndication(modifier = Modifier.fillMaxWidth())
+                        }
+                    }
+                }
+
+                // Constrained to the left of the lock icon (in left-to-right layouts).
+                with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
+
+                with(lockSection) { LockIcon() }
+
+                // Constrained to the right of the lock icon (in left-to-right layouts).
+                with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
+
+                // 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()) }
+                }
+            },
             modifier = modifier,
-        ) {
-            Column(
-                modifier = Modifier.fillMaxWidth().height { lockIconBounds.top },
-            ) {
-                with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
-                with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
-                with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
-                with(notificationSection) {
-                    Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
-                }
-                if (!isUdfpsVisible) {
-                    with(ambientIndicationSection) {
-                        AmbientIndication(modifier = Modifier.fillMaxWidth())
-                    }
-                }
-            }
+        ) { measurables, constraints ->
+            check(measurables.size == 5)
+            val (
+                aboveLockIconMeasurable,
+                startSideShortcutMeasurable,
+                lockIconMeasurable,
+                endSideShortcutMeasurable,
+                belowLockIconMeasurable,
+            ) = measurables
 
-            val shortcutSizePx =
-                with(LocalDensity.current) { bottomAreaSection.shortcutSizeDp().toSize() }
+            val noMinConstraints =
+                constraints.copy(
+                    minWidth = 0,
+                    minHeight = 0,
+                )
 
-            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
-                            }
+            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],
+                )
 
-                        IntOffset(0, rowTop)
-                    },
-            ) {
-                Spacer(Modifier.weight(1f))
+            val aboveLockIconPlaceable =
+                aboveLockIconMeasurable.measure(
+                    noMinConstraints.copy(maxHeight = lockIconBounds.top)
+                )
+            val startSideShortcutPlaceable = startSideShortcutMeasurable.measure(noMinConstraints)
+            val endSideShortcutPlaceable = endSideShortcutMeasurable.measure(noMinConstraints)
+            val belowLockIconPlaceable =
+                belowLockIconMeasurable.measure(
+                    noMinConstraints.copy(maxHeight = constraints.maxHeight - lockIconBounds.bottom)
+                )
 
-                with(bottomAreaSection) { Shortcut(isStart = true) }
-
-                Spacer(Modifier.weight(1f))
-
-                with(lockSection) {
-                    LockIcon(
-                        modifier =
-                            Modifier.width { lockIconBounds.width() }
-                                .height { lockIconBounds.height() }
-                    )
-                }
-
-                Spacer(Modifier.weight(1f))
-
-                with(bottomAreaSection) { Shortcut(isStart = false) }
-
-                Spacer(Modifier.weight(1f))
-            }
-
-            Column(modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)) {
-                if (isUdfpsVisible) {
-                    with(ambientIndicationSection) {
-                        AmbientIndication(modifier = Modifier.fillMaxWidth())
-                    }
-                }
-
-                with(bottomAreaSection) {
-                    IndicationArea(
-                        modifier =
-                            Modifier.fillMaxWidth()
-                                .padding(
-                                    horizontal =
-                                        dimensionResource(
-                                            R.dimen.keyguard_affordance_horizontal_offset
-                                        )
-                                )
-                                .padding(
-                                    bottom =
-                                        dimensionResource(
-                                            R.dimen.keyguard_affordance_vertical_offset
-                                        )
-                                )
-                                .heightIn(min = shortcutSizeDp().height),
-                    )
-                }
+            layout(constraints.maxWidth, constraints.maxHeight) {
+                aboveLockIconPlaceable.place(
+                    x = 0,
+                    y = 0,
+                )
+                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,
+                )
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 53e4be3..db20f65 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -19,7 +19,6 @@
 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 @@
     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 @@
                 falsingManager = falsingManager,
                 vibratorHelper = vibratorHelper,
                 indicationController = indicationController,
+                modifier =
+                    if (applyPadding) {
+                        Modifier.shortcutPadding()
+                    } else {
+                        Modifier
+                    }
             )
         }
     }
@@ -113,7 +97,7 @@
     ) {
         MovableElement(
             key = IndicationAreaElementKey,
-            modifier = modifier,
+            modifier = modifier.shortcutPadding(),
         ) {
             IndicationArea(
                 indicationAreaViewModel = indicationAreaViewModel,
@@ -218,6 +202,14 @@
             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")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 8bbe424b..d93863d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -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.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 @@
             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 @@
      * 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 @@
         val lockIconRadiusPx = (defaultDensity * 36).toInt()
 
         val udfpsLocation = authController.udfpsLocation
-        return if (authController.isUdfpsSupported && udfpsLocation != null) {
-            centerLockIcon(udfpsLocation, authController.udfpsRadius)
-        } else {
-            val scaleFactor = authController.scaleFactor
-            val bottomPaddingPx =
-                context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
-            val heightPx = windowViewBounds.bottom.toFloat()
+        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()
-                ),
-                lockIconRadiusPx * scaleFactor
-            )
-        }
-    }
+                Pair(
+                    IntOffset(
+                        x = (widthPx / 2).toInt(),
+                        y =
+                            (heightPx - ((bottomPaddingPx + lockIconRadiusPx) * scaleFactor))
+                                .toInt(),
+                    ),
+                    (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)
     }
 }