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

Commit 3441da44 authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 13185465 from 868534d2 to 25Q3-release

Change-Id: Icce52368152bcb23cb0a2321e368fdd7a1f57583
parents e50102de 868534d2
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -143,3 +143,20 @@ flag {
    description: "Enables squeeze effect on power button long press launching Gemini"
    bug: "396099245"
}

flag {
  name: "cursor_hot_corner"
  namespace: "systemui"
  description: "Enables hot corner navigation by cursor"
  bug: "397182595"
}

flag {
    name: "smartspace_remoteviews_intent_handler"
    namespace: "systemui"
    description: "Enables Smartspace RemoteViews intent handling on lockscreen"
    bug: "399416038"
    metadata {
         purpose: PURPOSE_BUGFIX
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -641,7 +641,7 @@ constructor(
            ComponentKey(ComponentName(packageName, packageName + EMPTY_CLASS_NAME), user)

        // Ensures themed bitmaps in the icon cache are invalidated
        @JvmField val RELEASE_VERSION = if (Flags.forceMonochromeAppIcons()) 8 else 7
        @JvmField val RELEASE_VERSION = if (Flags.forceMonochromeAppIcons()) 10 else 9

        @JvmField val TABLE_NAME = "icons"
        @JvmField val COLUMN_ROWID = "rowid"
+71 −10
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.mechanics

import android.util.Log
import androidx.compose.runtime.FloatState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@@ -28,6 +29,7 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.util.fastCoerceAtLeast
import androidx.compose.ui.util.fastCoerceIn
import androidx.compose.ui.util.fastIsFinite
import androidx.compose.ui.util.lerp
import androidx.compose.ui.util.packFloats
import androidx.compose.ui.util.unpackFloat1
@@ -196,6 +198,13 @@ class MotionValue(

            try {
                debugIsAnimating = true

                // indicates whether withFrameNanos is called continuously (as opposed to being
                // suspended for an undetermined amount of time in between withFrameNanos).
                // This is essential after `withFrameNanos` returned: if true at this point,
                // currentAnimationTimeNanos - lastFrameNanos is the duration of the last frame.
                var isAnimatingUninterrupted = false

                while (continueRunning.invoke(this@MotionValue)) {

                    withFrameNanos { frameTimeNanos ->
@@ -222,6 +231,19 @@ class MotionValue(
                    // cause a re-computation if the current state is being read before the next
                    // frame).

                    if (isAnimatingUninterrupted) {
                        val currentDirectMapped = currentDirectMapped
                        val lastDirectMapped =
                            lastSegment.mapping.map(lastInput) - lastAnimation.targetValue

                        val frameDuration =
                            (currentAnimationTimeNanos - lastFrameTimeNanos) / 1_000_000_000.0
                        val staticDelta = (currentDirectMapped - lastDirectMapped)
                        directMappedVelocity = (staticDelta / frameDuration).toFloat()
                    } else {
                        directMappedVelocity = 0f
                    }

                    var scheduleNextFrame = !isStable
                    if (capturedSegment != currentSegment) {
                        capturedSegment = currentSegment
@@ -273,6 +295,7 @@ class MotionValue(
                            )
                    }

                    isAnimatingUninterrupted = scheduleNextFrame
                    if (scheduleNextFrame) {
                        continue
                    }
@@ -374,6 +397,11 @@ class MotionValue(
    private var lastAnimation: DiscontinuityAnimation by
        mutableStateOf(DiscontinuityAnimation.None, referentialEqualityPolicy())

    // The change velocity of the `currentDirectMapped`, in `units/sec`. Only non-zero if the
    // animation loop is processing every frame (while animating or while the input changes
    // continuously).
    private var directMappedVelocity: Float = 0f

    // ---- Last frame's input and output ----------------------------------------------------------

    // The state below captures relevant input values (including frame time) and the computed spring
@@ -670,10 +698,23 @@ class MotionValue(
            SegmentChangeType.Direction,
            SegmentChangeType.Spec -> {
                // Determine the delta in the output, as produced by the old and new mapping.
                val delta =
                    currentSegment.mapping.map(currentInput) - lastSegment.mapping.map(currentInput)
                val currentMapping = currentSegment.mapping.map(currentInput)
                val lastMapping = lastSegment.mapping.map(currentInput)
                val delta = currentMapping - lastMapping

                val deltaIsFinite = delta.fastIsFinite()
                if (!deltaIsFinite) {
                    Log.wtf(
                        TAG,
                        "Delta between mappings is undefined!\n" +
                            "  MotionValue: $label\n" +
                            "  input: $currentInput\n" +
                            "  lastMapping: $lastMapping (lastSegment: $lastSegment)\n" +
                            "  currentMapping: $currentMapping (currentSegment: $currentSegment)",
                    )
                }

                if (delta == 0f) {
                if (delta == 0f || !deltaIsFinite) {
                    // Nothing new to animate.
                    lastAnimation
                } else {
@@ -687,7 +728,7 @@ class MotionValue(
                    val newTarget = delta - lastSpringState.displacement
                    DiscontinuityAnimation(
                        newTarget,
                        SpringState(-newTarget, lastSpringState.velocity),
                        SpringState(-newTarget, lastSpringState.velocity + directMappedVelocity),
                        springParameters,
                        lastFrameTimeNanos,
                    )
@@ -765,14 +806,28 @@ class MotionValue(
                            )
                        lastAnimationTime = nextBreakpointCrossTime

                        val beforeBreakpoint = mappings[segmentIndex].map(nextBreakpoint.position)
                        val afterBreakpoint =
                            mappings[segmentIndex + directionOffset].map(nextBreakpoint.position)
                        val mappingBefore = mappings[segmentIndex]
                        val beforeBreakpoint = mappingBefore.map(nextBreakpoint.position)
                        val mappingAfter = mappings[segmentIndex + directionOffset]
                        val afterBreakpoint = mappingAfter.map(nextBreakpoint.position)

                        val delta = afterBreakpoint - beforeBreakpoint
                        springTarget += delta
                        springState = springState.addDisplacement(-delta)
                        val deltaIsFinite = delta.fastIsFinite()
                        if (!deltaIsFinite) {
                            Log.wtf(
                                TAG,
                                "Delta between breakpoints is undefined!\n" +
                                    "  MotionValue: $label\n" +
                                    "  position: ${nextBreakpoint.position}\n" +
                                    "  before: $beforeBreakpoint (mapping: $mappingBefore)\n" +
                                    "  after: $afterBreakpoint (mapping: $mappingAfter)",
                            )
                        }

                        if (deltaIsFinite) {
                            springTarget += delta
                            springState = springState.nudge(displacementDelta = -delta)
                        }
                        segmentIndex += directionOffset
                        lastBreakpoint = nextBreakpoint
                        guaranteeState =
@@ -793,6 +848,10 @@ class MotionValue(
                            }
                    }

                    if (springState.displacement != 0f) {
                        springState = springState.nudge(velocityDelta = directMappedVelocity)
                    }

                    val tightened =
                        currentGuaranteeState.updatedSpringParameters(
                            currentSegment.entryBreakpoint
@@ -828,7 +887,9 @@ class MotionValue(
    }

    private val currentDirectMapped: Float
        get() = currentSegment.mapping.map(currentInput()) - currentAnimation.targetValue
        get() {
            return currentSegment.mapping.map(currentInput()) - currentAnimation.targetValue
        }

    private val currentAnimatedDelta: Float
        get() = currentAnimation.targetValue + currentSpringState.displacement
+141 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.mechanics.behavior

import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.requireGraphicsContext
import androidx.compose.ui.util.fastCoerceAtLeast
import androidx.compose.ui.util.fastCoerceIn
import androidx.compose.ui.util.lerp
import kotlin.math.min

/**
 * Draws the background of an edge container, and applies clipping to it.
 *
 * Intended to be used with a [EdgeContainerExpansionSpec] motion.
 */
fun Modifier.edgeContainerExpansionBackground(
    backgroundColor: Color,
    spec: EdgeContainerExpansionSpec,
): Modifier = this.then(EdgeContainerExpansionBackgroundElement(backgroundColor, spec))

internal class EdgeContainerExpansionBackgroundNode(
    var backgroundColor: Color,
    var spec: EdgeContainerExpansionSpec,
) : Modifier.Node(), DrawModifierNode {

    private var graphicsLayer: GraphicsLayer? = null
    private var lastOutlineSize = Size.Zero

    fun invalidateOutline() {
        lastOutlineSize = Size.Zero
    }

    override fun onAttach() {
        graphicsLayer = requireGraphicsContext().createGraphicsLayer().apply { clip = true }
    }

    override fun onDetach() {
        requireGraphicsContext().releaseGraphicsLayer(checkNotNull(graphicsLayer))
    }

    override fun ContentDrawScope.draw() {
        val height = size.height

        // The width is growing between visibleHeight and detachHeight
        val visibleHeight = spec.visibleHeight.toPx()
        val widthFraction =
            ((height - visibleHeight) / (spec.detachHeight.toPx() - visibleHeight)).fastCoerceIn(
                0f,
                1f,
            )
        val width = size.width - lerp(spec.widthOffset.toPx(), 0f, widthFraction)
        val horizontalInset = (size.width - width) / 2f

        // The radius is growing at the beginning of the transition
        val radius = height.fastCoerceIn(spec.minRadius.toPx(), spec.radius.toPx())

        // Draw (at most) the bottom half of the rounded corner rectangle, aligned to the bottom.
        val upperHeight = height - radius

        // The rounded rect is drawn at 2x the radius height, to avoid smaller corner radii.
        // The clipRect limits this to the relevant part (-1 to avoid a hairline gap being visible
        // between this and the fill below.
        clipRect(top = (upperHeight - 1).fastCoerceAtLeast(0f)) {
            drawRoundRect(
                color = backgroundColor,
                cornerRadius = CornerRadius(radius),
                size = Size(width, radius * 2f),
                topLeft = Offset(horizontalInset, size.height - radius * 2f),
            )
        }

        if (upperHeight > 0) {
            // Fill the space above the bottom shape.
            drawRect(
                color = backgroundColor,
                topLeft = Offset(horizontalInset, 0f),
                size = Size(width, upperHeight),
            )
        }

        // Draw the node's content in a separate layer.
        val graphicsLayer = checkNotNull(graphicsLayer)
        graphicsLayer.record { this@draw.drawContent() }

        if (size != lastOutlineSize) {
            // The clip outline is a rounded corner shape matching the bottom of the shape.
            // At the top, the rounded corner shape extends by radiusPx above top.
            // This clipping thus would not prevent the containers content to overdraw at the top,
            // however this is off-screen anyways.
            val top = min(-radius, height - radius * 2f)

            val rect = Rect(left = horizontalInset, top = top, right = width, bottom = height)
            graphicsLayer.setRoundRectOutline(rect.topLeft, rect.size, radius)
            lastOutlineSize = size
        }

        this.drawLayer(graphicsLayer)
    }
}

private data class EdgeContainerExpansionBackgroundElement(
    val backgroundColor: Color,
    val spec: EdgeContainerExpansionSpec,
) : ModifierNodeElement<EdgeContainerExpansionBackgroundNode>() {
    override fun create(): EdgeContainerExpansionBackgroundNode =
        EdgeContainerExpansionBackgroundNode(backgroundColor, spec)

    override fun update(node: EdgeContainerExpansionBackgroundNode) {
        node.backgroundColor = backgroundColor
        if (node.spec != spec) {
            node.spec = spec
            node.invalidateOutline()
        }
    }
}
+157 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

@file:OptIn(ExperimentalMaterial3ExpressiveApi::class)

package com.android.mechanics.behavior

import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MotionScheme
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastCoerceIn
import androidx.compose.ui.util.lerp
import com.android.mechanics.spec.Breakpoint
import com.android.mechanics.spec.BreakpointKey
import com.android.mechanics.spec.DirectionalMotionSpec
import com.android.mechanics.spec.InputDirection
import com.android.mechanics.spec.Mapping
import com.android.mechanics.spec.MotionSpec
import com.android.mechanics.spec.OnChangeSegmentHandler
import com.android.mechanics.spec.SegmentData
import com.android.mechanics.spec.SegmentKey
import com.android.mechanics.spec.builder
import com.android.mechanics.spec.reverseBuilder
import com.android.mechanics.spring.SpringParameters

/** Motion spec for a vertically expandable container. */
class EdgeContainerExpansionSpec(
    val visibleHeight: Dp = Defaults.VisibleHeight,
    val preDetachRatio: Float = Defaults.PreDetachRatio,
    val detachHeight: Dp = Defaults.DetachHeight,
    val attachHeight: Dp = Defaults.AttachHeight,
    val widthOffset: Dp = Defaults.WidthOffset,
    val minRadius: Dp = Defaults.MinRadius,
    val radius: Dp = Defaults.Radius,
    val attachSpring: SpringParameters = Defaults.AttachSpring,
    val detachSpring: SpringParameters = Defaults.DetachSpring,
    val opacitySpring: SpringParameters = Defaults.OpacitySpring,
) {
    fun createHeightSpec(motionScheme: MotionScheme, density: Density): MotionSpec {
        return with(density) {
            val spatialSpring = SpringParameters(motionScheme.defaultSpatialSpec())

            val detachSpec =
                DirectionalMotionSpec.builder(
                        initialMapping = Mapping.Zero,
                        defaultSpring = spatialSpring,
                    )
                    .toBreakpoint(0f, key = Breakpoints.Attach)
                    .continueWith(Mapping.Linear(preDetachRatio))
                    .toBreakpoint(detachHeight.toPx(), key = Breakpoints.Detach)
                    .completeWith(Mapping.Identity, detachSpring)

            val attachSpec =
                DirectionalMotionSpec.reverseBuilder(defaultSpring = spatialSpring)
                    .toBreakpoint(attachHeight.toPx(), key = Breakpoints.Detach)
                    .completeWith(mapping = Mapping.Zero, attachSpring)

            val segmentHandlers =
                mapOf<SegmentKey, OnChangeSegmentHandler>(
                    SegmentKey(Breakpoints.Detach, Breakpoint.maxLimit.key, InputDirection.Min) to
                        { currentSegment, _, newDirection ->
                            if (newDirection != currentSegment.direction) currentSegment else null
                        },
                    SegmentKey(Breakpoints.Attach, Breakpoints.Detach, InputDirection.Max) to
                        { currentSegment: SegmentData, newInput: Float, newDirection: InputDirection
                            ->
                            if (newDirection != currentSegment.direction && newInput >= 0)
                                currentSegment
                            else null
                        },
                )

            MotionSpec(
                maxDirection = detachSpec,
                minDirection = attachSpec,
                segmentHandlers = segmentHandlers,
            )
        }
    }

    fun createWidthSpec(
        intrinsicWidth: Float,
        motionScheme: MotionScheme,
        density: Density,
    ): MotionSpec {
        return with(density) {
            MotionSpec.builder(
                    SpringParameters(motionScheme.defaultSpatialSpec()),
                    initialMapping = { input ->
                        val fraction = (input / detachHeight.toPx()).fastCoerceIn(0f, 1f)
                        intrinsicWidth - lerp(widthOffset.toPx(), 0f, fraction)
                    },
                )
                .complete()
        }
    }

    fun createAlphaSpec(motionScheme: MotionScheme, density: Density): MotionSpec {
        return with(density) {
            val detachSpec =
                DirectionalMotionSpec.builder(
                        SpringParameters(motionScheme.defaultEffectsSpec()),
                        initialMapping = Mapping.Zero,
                    )
                    .toBreakpoint(visibleHeight.toPx())
                    .completeWith(Mapping.One, opacitySpring)

            val attachSpec =
                DirectionalMotionSpec.builder(
                        SpringParameters(motionScheme.defaultEffectsSpec()),
                        initialMapping = Mapping.Zero,
                    )
                    .toBreakpoint(visibleHeight.toPx())
                    .completeWith(Mapping.One, opacitySpring)

            MotionSpec(maxDirection = detachSpec, minDirection = attachSpec)
        }
    }

    companion object {
        object Breakpoints {
            val Attach = BreakpointKey("EdgeContainerExpansion::Attach")
            val Detach = BreakpointKey("EdgeContainerExpansion::Detach")
        }

        object Defaults {
            val VisibleHeight = 24.dp
            val PreDetachRatio = .25f
            val DetachHeight = 80.dp
            val AttachHeight = 40.dp

            val WidthOffset = 28.dp

            val MinRadius = 28.dp
            val Radius = 46.dp

            val AttachSpring = SpringParameters(stiffness = 380f, dampingRatio = 0.9f)
            val DetachSpring = SpringParameters(stiffness = 380f, dampingRatio = 0.9f)
            val OpacitySpring = SpringParameters(stiffness = 1200f, dampingRatio = 0.99f)
        }
    }
}
Loading