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

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

Snap for 13104745 from 2c0ce439 to 25Q2-release

Change-Id: Ib2dc0f5ba4b5170487e110a33974dd277dc717fd
parents ae2f9ca1 2c0ce439
Loading
Loading
Loading
Loading
+13 −3
Original line number Diff line number Diff line
@@ -231,8 +231,14 @@ public class BaseIconFactory implements AutoCloseable {
        if (adaptiveIcon instanceof Extender extender) {
            info = extender.getExtendedInfo(bitmap, color, this, scale[0]);
        } else if (IconProvider.ATLEAST_T && mThemeController != null && adaptiveIcon != null) {
            info.setThemedBitmap(mThemeController.createThemedBitmap(
                    adaptiveIcon, info, this, options == null ? null : options.mSourceHint));
            info.setThemedBitmap(
                    mThemeController.createThemedBitmap(
                        adaptiveIcon,
                        info,
                        this,
                        options == null ? null : options.mSourceHint
                    )
            );
        }
        info = info.withFlags(getBitmapFlagOp(options));
        return info;
@@ -276,10 +282,14 @@ public class BaseIconFactory implements AutoCloseable {
    }

    @NonNull
    protected Path getShapePath(AdaptiveIconDrawable drawable, Rect iconBounds) {
    public Path getShapePath(AdaptiveIconDrawable drawable, Rect iconBounds) {
        return drawable.getIconMask();
    }

    public float getIconScale() {
        return 1f;
    }

    @NonNull
    public Bitmap getWhiteShadowLayer() {
        if (mWhiteShadowLayer == null) {
+3 −2
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.graphics.ColorFilter;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
@@ -99,12 +100,12 @@ public class MonochromeIconFactory extends Drawable {
     * Creates a monochrome version of the provided drawable
     */
    @WorkerThread
    public Drawable wrap(AdaptiveIconDrawable icon) {
    public Drawable wrap(AdaptiveIconDrawable icon, Path shapePath, Float iconScale) {
        mFlatCanvas.drawColor(Color.BLACK);
        drawDrawable(icon.getBackground());
        drawDrawable(icon.getForeground());
        generateMono();
        return new ClippedMonoDrawable(this);
        return new ClippedMonoDrawable(this, shapePath, iconScale);
    }

    @WorkerThread
+21 −9
Original line number Diff line number Diff line
@@ -24,7 +24,8 @@ import android.graphics.Bitmap.Config.HARDWARE
import android.graphics.BlendMode.SRC_IN
import android.graphics.BlendModeColorFilter
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Path
import android.graphics.Rect
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
@@ -55,7 +56,14 @@ class MonoIconThemeController(
        factory: BaseIconFactory,
        sourceHint: SourceHint?,
    ): ThemedBitmap? {
        val mono = getMonochromeDrawable(icon, info, sourceHint?.isFileDrawable ?: false)
        val mono =
            getMonochromeDrawable(
                icon,
                info,
                factory.getShapePath(icon, Rect(0, 0, info.icon.width, info.icon.height)),
                factory.iconScale,
                sourceHint?.isFileDrawable ?: false,
            )
        if (mono != null) {
            return MonoThemedBitmap(
                factory.createIconBitmap(mono, ICON_VISIBLE_AREA_FACTOR, MODE_ALPHA),
@@ -74,14 +82,16 @@ class MonoIconThemeController(
    private fun getMonochromeDrawable(
        base: AdaptiveIconDrawable,
        info: BitmapInfo,
        shapePath: Path,
        iconScale: Float,
        isFileDrawable: Boolean,
    ): Drawable? {
        val mono = base.monochrome
        if (mono != null) {
            return ClippedMonoDrawable(mono)
            return ClippedMonoDrawable(mono, shapePath, iconScale)
        }
        if (Flags.forceMonochromeAppIcons() && !isFileDrawable) {
            return MonochromeIconFactory(info.icon.width).wrap(base)
            return MonochromeIconFactory(info.icon.width).wrap(base, shapePath, iconScale)
        }
        return null
    }
@@ -136,14 +146,16 @@ class MonoIconThemeController(
        return monoDrawable?.let { AdaptiveIconDrawable(ColorDrawable(colors[0]), it) }
    }

    class ClippedMonoDrawable(base: Drawable?) :
        InsetDrawable(base, -AdaptiveIconDrawable.getExtraInsetFraction()) {
        private val mCrop = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null)
    class ClippedMonoDrawable(
        base: Drawable?,
        private val shapePath: Path,
        private val iconScale: Float,
    ) : InsetDrawable(base, -AdaptiveIconDrawable.getExtraInsetFraction()) {

        override fun draw(canvas: Canvas) {
            mCrop.bounds = bounds
            val saveCount = canvas.save()
            canvas.clipPath(mCrop.iconMask)
            canvas.clipPath(shapePath)
            canvas.scale(iconScale, iconScale, canvas.width / 2f, canvas.height / 2f)
            super.draw(canvas)
            canvas.restoreToCount(saveCount)
        }
+140 −97
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import androidx.compose.runtime.referentialEqualityPolicy
import androidx.compose.runtime.setValue
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.lerp
import androidx.compose.ui.util.packFloats
import androidx.compose.ui.util.unpackFloat1
@@ -43,10 +45,9 @@ import com.android.mechanics.spring.SpringState
import com.android.mechanics.spring.calculateUpdatedState
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.max
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext

/**
 * Computes an animated [output] value, by mapping the [currentInput] according to the [spec].
@@ -155,101 +156,143 @@ class MotionValue(
     *
     * Internally, this method does suspend, unless there are animations ongoing.
     */
    suspend fun keepRunning(): Nothing = coroutineScope {
        check(!isActive) { "keepRunning() invoked while already running" }
    suspend fun keepRunning(): Nothing {
        keepRunningWhile { true }

        // `keepRunning` above will never finish,
        throw AssertionError("Unreachable code")
    }

    /**
     * Keeps the [MotionValue]'s animated output running while [continueRunning] returns `true`.
     *
     * When [continueRunning] returns `false`, the coroutine will end by the next frame.
     *
     * To keep the [MotionValue] running until the current animations are complete, check for
     * `isStable` as well.
     *
     * ```kotlin
     * motionValue.keepRunningWhile { !shouldEnd() || !isStable }
     * ```
     */
    suspend fun keepRunningWhile(continueRunning: MotionValue.() -> Boolean) =
        withContext(CoroutineName("MotionValue($label)")) {
            check(!isActive) { "MotionValue($label) is already running" }

            isActive = true
        try {
            // The purpose of this implementation is to run an animation frame (via withFrameNanos)
            // whenever the input changes, or the spring is still settling, but otherwise just
            // suspend.

            // Used to suspend when no animations are running, and to wait for a wakeup signal.
            val wakeupChannel = Channel<Unit>(capacity = Channel.CONFLATED)
            // These `previous*` values will be applied to the `last*` values, at the beginning
            // of the each new frame.

            // `true` while the spring is settling.
            var runAnimationFrames = !isStable
            launch {
                // TODO(b/383979536) use a SnapshotStateObserver instead
                snapshotFlow {
                        // observe all input values
                        var result = spec.hashCode()
                        result = result * 31 + currentInput().hashCode()
                        result = result * 31 + currentDirection.hashCode()
                        result = result * 31 + currentGestureDragOffset.hashCode()

                        // Track whether the spring needs animation frames to finish
                        // In fact, whether the spring is settling is the only relevant bit to
                        // export from here. For everything else, just cause the flow to emit a
                        // different value (hence the hashing)
                        (result shl 1) + if (isStable) 0 else 1
                    }
                    .collect { hashedState ->
                        // while the 'runAnimationFrames' bit was set on the result
                        runAnimationFrames = (hashedState and 1) != 0
                        // nudge the animation runner in case its sleeping.
                        wakeupChannel.send(Unit)
                    }
            }

            while (true) {
                if (!runAnimationFrames) {
                    // While the spring does not need animation frames (its stable), wait until
                    // woken up - this can be for a single frame after an input change.
                    debugIsAnimating = false
                    wakeupChannel.receive()
                }
            // TODO(b/397837971): Encapsulate the state in a StateRecord.
            var capturedSegment = currentSegment
            var capturedGuaranteeState = currentGuaranteeState
            var capturedAnimation = currentAnimation
            var capturedSpringState = currentSpringState
            var capturedFrameTimeNanos = currentAnimationTimeNanos
            var capturedInput = currentInput()
            var capturedGestureDragOffset = currentGestureDragOffset
            var capturedDirection = currentDirection

            try {
                debugIsAnimating = true
                withFrameNanos { frameTimeNanos -> currentAnimationTimeNanos = frameTimeNanos }
                while (continueRunning.invoke(this@MotionValue)) {

                    withFrameNanos { frameTimeNanos ->
                        currentAnimationTimeNanos = frameTimeNanos

                        // With the new frame started, copy

                        lastSegment = capturedSegment
                        lastGuaranteeState = capturedGuaranteeState
                        lastAnimation = capturedAnimation
                        lastSpringState = capturedSpringState
                        lastFrameTimeNanos = capturedFrameTimeNanos
                        lastInput = capturedInput
                        lastGestureDragOffset = capturedGestureDragOffset
                    }

                    // At this point, the complete frame is done (including layout, drawing and
                // everything else). What follows next is similar what one would do in a
                // `SideEffect`, were this composable code:
                // If during the last frame, a new animation was started, or a new segment entered,
                // this state is copied over. If nothing changed, the computed `current*` state will
                // be the same, it won't have a side effect.

                // Capturing the state here is required since crossing a breakpoint is an event -
                // the code has to record that this happened.

                // Important - capture all values first, and only afterwards update the state.
                // Interleaving read and update might trigger immediate re-computations.
                val newSegment = currentSegment
                val newGuaranteeState = currentGuaranteeState
                val newAnimation = currentAnimation
                val newSpringState = currentSpringState

                // Capture the last frames input.
                lastFrameTimeNanos = currentAnimationTimeNanos
                lastInput = currentInput()
                lastGestureDragOffset = currentGestureDragOffset
                // Not capturing currentDirection and spec explicitly, they are included in
                // lastSegment

                // Update the state to the computed `current*` values
                lastSegment = newSegment
                lastGuaranteeState = newGuaranteeState
                lastAnimation = newAnimation
                lastSpringState = newSpringState
                    // everything else), and this MotionValue has been updated.

                    // Capture the `current*` MotionValue state, so that it can be applied as the
                    // `last*` state when the next frame starts. Its imperative to capture at this
                    // point already (since the input could change before the next frame starts),
                    // while at the same time not already applying the `last*` state (as this would
                    // cause a re-computation if the current state is being read before the next
                    // frame).

                    var scheduleNextFrame = !isStable
                    if (capturedSegment != currentSegment) {
                        capturedSegment = currentSegment
                        scheduleNextFrame = true
                    }

                    if (capturedGuaranteeState != currentGuaranteeState) {
                        capturedGuaranteeState = currentGuaranteeState
                        scheduleNextFrame = true
                    }

                    if (capturedAnimation != currentAnimation) {
                        capturedAnimation = currentAnimation
                        scheduleNextFrame = true
                    }

                    if (capturedSpringState != currentSpringState) {
                        capturedSpringState = currentSpringState
                        scheduleNextFrame = true
                    }

                    if (capturedInput != currentInput()) {
                        capturedInput = currentInput()
                        scheduleNextFrame = true
                    }

                    if (capturedGestureDragOffset != currentGestureDragOffset) {
                        capturedGestureDragOffset = currentGestureDragOffset
                        scheduleNextFrame = true
                    }

                    if (capturedDirection != currentDirection) {
                        capturedDirection = currentDirection
                        scheduleNextFrame = true
                    }

                    capturedFrameTimeNanos = currentAnimationTimeNanos

                    debugInspector?.run {
                        frame =
                            FrameData(
                            lastInput,
                            currentDirection,
                            lastGestureDragOffset,
                            lastFrameTimeNanos,
                            lastSpringState,
                            lastSegment,
                            lastAnimation,
                                capturedInput,
                                capturedDirection,
                                capturedGestureDragOffset,
                                capturedFrameTimeNanos,
                                capturedSpringState,
                                capturedSegment,
                                capturedAnimation,
                            )
                    }

                    if (scheduleNextFrame) {
                        continue
                    }

            // Keep the compiler happy - the while (true) {} above will not complete, yet the
            // compiler wants a return value.
            @Suppress("UNREACHABLE_CODE") awaitCancellation()
                    debugIsAnimating = false
                    snapshotFlow {
                            val wakeup =
                                !continueRunning.invoke(this@MotionValue) ||
                                    spec != capturedSegment.spec ||
                                    currentInput() != capturedInput ||
                                    currentDirection != capturedDirection ||
                                    currentGestureDragOffset != capturedGestureDragOffset
                            wakeup
                        }
                        .first { it }
                    debugIsAnimating = true
                }
            } finally {
                isActive = false
                debugIsAnimating = false
            }
        }

@@ -507,7 +550,7 @@ class MotionValue(
     * last frame, but that is likely good enough.
     */
    private fun lastFrameFractionOfPosition(position: Float): Float {
        return ((position - lastInput) / (currentInput() - lastInput)).coerceIn(0f, 1f)
        return ((position - lastInput) / (currentInput() - lastInput)).fastCoerceIn(0f, 1f)
    }

    /**
@@ -674,7 +717,7 @@ class MotionValue(
                    var segmentIndex = sourceIndex
                    while (segmentIndex != targetIndex) {
                        val nextBreakpoint =
                            breakpoints[segmentIndex + directionOffset.coerceAtLeast(0)]
                            breakpoints[segmentIndex + directionOffset.fastCoerceAtLeast(0)]

                        val nextBreakpointFrameFraction =
                            lastFrameFractionOfPosition(nextBreakpoint.position)
@@ -890,7 +933,7 @@ internal value class GuaranteeState(val packedValue: Long) {
    fun withCurrentValue(value: Float, direction: InputDirection): GuaranteeState {
        if (isInactive) return Inactive

        val delta = ((value - start) * direction.sign).coerceAtLeast(0f)
        val delta = ((value - start) * direction.sign).fastCoerceAtLeast(0f)
        return GuaranteeState(start, max(delta, maxDelta))
    }

+4 −2
Original line number Diff line number Diff line
@@ -43,6 +43,8 @@ import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastCoerceAtLeast
import androidx.compose.ui.util.fastCoerceAtMost
import androidx.compose.ui.util.fastForEachIndexed
import com.android.mechanics.MotionValue
import com.android.mechanics.spec.DirectionalMotionSpec
@@ -352,10 +354,10 @@ private fun DrawScope.drawDirectionalSpec(
        val mapping = spec.mappings[segmentIndex]
        val startBreakpoint = spec.breakpoints[segmentIndex]
        val segmentStart = startBreakpoint.position
        val fromInput = segmentStart.coerceAtLeast(inputRange.start)
        val fromInput = segmentStart.fastCoerceAtLeast(inputRange.start)
        val endBreakpoint = spec.breakpoints[segmentIndex + 1]
        val segmentEnd = endBreakpoint.position
        val toInput = segmentEnd.coerceAtMost(inputRange.endInclusive)
        val toInput = segmentEnd.fastCoerceAtMost(inputRange.endInclusive)

        // TODO add support for functions that are not linear
        val fromY = mapPointInOutputToY(mapping.map(fromInput), outputRange)
Loading