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

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

Snap for 13538949 from e5d3f466 to 25Q3-release

Change-Id: I319a1220b54de27aa81ac8d6e02f667d59e46fb5
parents bb83b44b e5d3f466
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -126,6 +126,8 @@ data class MotionSpec(
            ?: segmentAtInput(newPosition, newDirection)
    }

    override fun toString() = toDebugString()

    companion object {
        /**
         * Default spring parameters for the reset spring. Matches the Fast Spatial spring of the
@@ -224,6 +226,8 @@ data class DirectionalMotionSpec(
        return result
    }

    override fun toString() = toDebugString()

    companion object {
        /* Empty spec, the full input domain is mapped to output using [Mapping.identity]. */
        val Empty =
+121 −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.spec

/** Returns a string representation of the [MotionSpec] for debugging by humans. */
fun MotionSpec.toDebugString(): String {
    return buildString {
            if (minDirection == maxDirection) {
                appendLine("unidirectional:")
                appendLine(minDirection.toDebugString().prependIndent("  "))
            } else {
                appendLine("maxDirection:")
                appendLine(maxDirection.toDebugString().prependIndent("  "))
                appendLine("minDirection:")
                appendLine(minDirection.toDebugString().prependIndent("  "))
            }

            if (segmentHandlers.isNotEmpty()) {
                appendLine("segmentHandlers:")
                segmentHandlers.keys.forEach {
                    appendIndent(2)
                    appendSegmentKey(it)
                    appendLine()
                }
            }
        }
        .trim()
}

/** Returns a string representation of the [DirectionalMotionSpec] for debugging by humans. */
fun DirectionalMotionSpec.toDebugString(): String {
    return buildString {
            appendBreakpointLine(breakpoints.first())
            for (i in mappings.indices) {
                appendMappingLine(mappings[i], indent = 2)
                semantics.forEach { appendSemanticsLine(it.key, it.values[i], indent = 4) }
                appendBreakpointLine(breakpoints[i + 1])
            }
        }
        .trim()
}

private fun StringBuilder.appendIndent(indent: Int) {
    repeat(indent) { append(' ') }
}

private fun StringBuilder.appendBreakpointLine(breakpoint: Breakpoint, indent: Int = 0) {
    appendIndent(indent)
    append("@")
    append(breakpoint.position)

    append(" [")
    appendBreakpointKey(breakpoint.key)
    append("]")

    if (breakpoint.guarantee != Guarantee.None) {
        append(" guarantee=")
        append(breakpoint.key.debugLabel)
    }

    if (!breakpoint.spring.isSnapSpring) {
        append(" spring=")
        append(breakpoint.spring.stiffness)
        append("/")
        append(breakpoint.spring.dampingRatio)
    }

    appendLine()
}

private fun StringBuilder.appendBreakpointKey(key: BreakpointKey) {
    if (key.debugLabel != null) {
        append(key.debugLabel)
        append("|")
    }
    append("id:0x")
    append(System.identityHashCode(key.identity).toString(16).padStart(8, '0'))
}

private fun StringBuilder.appendSegmentKey(key: SegmentKey) {
    appendBreakpointKey(key.minBreakpoint)
    if (key.direction == InputDirection.Min) append(" << ") else append(" >> ")
    appendBreakpointKey(key.maxBreakpoint)
}

private fun StringBuilder.appendMappingLine(mapping: Mapping, indent: Int = 0) {
    appendIndent(indent)
    append(mapping.toString())
    appendLine()
}

private fun StringBuilder.appendSemanticsLine(
    semanticKey: SemanticKey<*>,
    value: Any?,
    indent: Int = 0,
) {
    appendIndent(indent)

    append(semanticKey.debugLabel)
    append("[id:0x")
    append(System.identityHashCode(semanticKey.identity).toString(16).padStart(8, '0'))
    append("]")

    append("=")
    append(value)
    appendLine()
}
+20 −29
Original line number Diff line number Diff line
@@ -102,12 +102,8 @@ internal open class DirectionalBuilderImpl(
            }
        }

        toBreakpointImpl(atPosition, key)
        toBreakpointImpl(atPosition, key, semantics)
        doAddBreakpointImpl(springSpec, guarantee)

        if (key != BreakpointKey.MaxLimit) {
            applySemantics(semantics)
        }
    }

    fun finalizeBuilderFn(breakpoint: Breakpoint) =
@@ -140,8 +136,7 @@ internal open class DirectionalBuilderImpl(
        key: BreakpointKey,
        semantics: List<SemanticValue<*>>,
    ) {
        applySemantics(semantics)
        toBreakpointImpl(breakpoint, key)
        toBreakpointImpl(breakpoint, key, semantics)
        jumpToImpl(from, spring, guarantee)
        continueWithTargetValueImpl(to)
    }
@@ -155,8 +150,7 @@ internal open class DirectionalBuilderImpl(
        key: BreakpointKey,
        semantics: List<SemanticValue<*>>,
    ) {
        applySemantics(semantics)
        toBreakpointImpl(breakpoint, key)
        toBreakpointImpl(breakpoint, key, semantics)
        jumpByImpl(delta, spring, guarantee)
        continueWithTargetValueImpl(to)
    }
@@ -170,8 +164,7 @@ internal open class DirectionalBuilderImpl(
        key: BreakpointKey,
        semantics: List<SemanticValue<*>>,
    ): CanBeLastSegment {
        applySemantics(semantics)
        toBreakpointImpl(breakpoint, key)
        toBreakpointImpl(breakpoint, key, semantics)
        jumpToImpl(from, spring, guarantee)
        continueWithFractionalInputImpl(fraction)
        return CanBeLastSegmentImpl
@@ -186,8 +179,7 @@ internal open class DirectionalBuilderImpl(
        key: BreakpointKey,
        semantics: List<SemanticValue<*>>,
    ): CanBeLastSegment {
        applySemantics(semantics)
        toBreakpointImpl(breakpoint, key)
        toBreakpointImpl(breakpoint, key, semantics)
        jumpByImpl(delta, spring, guarantee)
        continueWithFractionalInputImpl(fraction)
        return CanBeLastSegmentImpl
@@ -201,8 +193,7 @@ internal open class DirectionalBuilderImpl(
        key: BreakpointKey,
        semantics: List<SemanticValue<*>>,
    ): CanBeLastSegment {
        applySemantics(semantics)
        toBreakpointImpl(breakpoint, key)
        toBreakpointImpl(breakpoint, key, semantics)
        jumpToImpl(value, spring, guarantee)
        continueWithFixedValueImpl()
        return CanBeLastSegmentImpl
@@ -216,8 +207,7 @@ internal open class DirectionalBuilderImpl(
        key: BreakpointKey,
        semantics: List<SemanticValue<*>>,
    ): CanBeLastSegment {
        applySemantics(semantics)
        toBreakpointImpl(breakpoint, key)
        toBreakpointImpl(breakpoint, key, semantics)
        jumpByImpl(delta, spring, guarantee)
        continueWithFixedValueImpl()
        return CanBeLastSegmentImpl
@@ -231,21 +221,11 @@ internal open class DirectionalBuilderImpl(
        semantics: List<SemanticValue<*>>,
        mapping: Mapping,
    ): CanBeLastSegment {
        applySemantics(semantics)
        toBreakpointImpl(breakpoint, key)
        toBreakpointImpl(breakpoint, key, semantics)
        continueWithImpl(mapping, spring, guarantee)
        return CanBeLastSegmentImpl
    }

    private fun applySemantics(toApply: List<SemanticValue<*>>) {
        toApply.forEach { (key, value) ->
            getSemantics(key).apply {
                // applySemantics is called BEFORE adding the mapping
                set(mappings.size, value)
            }
        }
    }

    private fun continueWithTargetValueImpl(target: Float) {
        check(sourceValue.isFinite())

@@ -286,7 +266,11 @@ internal open class DirectionalBuilderImpl(
        mappings.add(mapping)
    }

    private fun toBreakpointImpl(atPosition: Float, key: BreakpointKey) {
    private fun toBreakpointImpl(
        atPosition: Float,
        key: BreakpointKey,
        semantics: List<SemanticValue<*>>,
    ) {
        check(breakpointPosition.isNaN())
        check(breakpointKey == null)

@@ -324,6 +308,13 @@ internal open class DirectionalBuilderImpl(

        breakpointPosition = atPosition
        breakpointKey = key

        semantics.forEach { (key, value) ->
            getSemantics(key).apply {
                // Last segment is guaranteed to be completed
                set(mappings.size, value)
            }
        }
    }

    private fun doAddBreakpointImpl(
+145 −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.spec

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.mechanics.spec.ChangeSegmentHandlers.PreventDirectionChangeWithinCurrentSegment
import com.android.mechanics.spec.builder.MotionBuilderContext
import com.android.mechanics.spec.builder.effectsDirectionalMotionSpec
import com.android.mechanics.spec.builder.spatialDirectionalMotionSpec
import com.android.mechanics.testing.FakeMotionSpecBuilderContext
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MotionSpecDebugFormatterTest : MotionBuilderContext by FakeMotionSpecBuilderContext.Default {

    @Test
    fun motionSpec_unidirectionalSpec_formatIsUseful() {
        val spec = MotionSpec(effectsDirectionalMotionSpec { fixedValue(0f, value = 1f) })

        assertThat(formatForTest(spec.toDebugString()))
            .isEqualTo(
                """
unidirectional:
  @-Infinity [built-in::min|id:0x1234cdef]
    Fixed(value=0.0)
  @0.0 [id:0x1234cdef] spring=1600.0/1.0
    Fixed(value=1.0)
  @Infinity [built-in::max|id:0x1234cdef]"""
                    .trimIndent()
            )
    }

    @Test
    fun motionSpec_bidirectionalSpec_formatIsUseful() {
        val spec =
            MotionSpec(
                spatialDirectionalMotionSpec(Mapping.Zero) { fixedValue(0f, value = 1f) },
                spatialDirectionalMotionSpec(Mapping.One) { fixedValue(0f, value = 0f) },
            )

        assertThat(formatForTest(spec.toDebugString()))
            .isEqualTo(
                """
maxDirection:
  @-Infinity [built-in::min|id:0x1234cdef]
    Fixed(value=0.0)
  @0.0 [id:0x1234cdef] spring=700.0/0.9
    Fixed(value=1.0)
  @Infinity [built-in::max|id:0x1234cdef]
minDirection:
  @-Infinity [built-in::min|id:0x1234cdef]
    Fixed(value=1.0)
  @0.0 [id:0x1234cdef] spring=700.0/0.9
    Fixed(value=0.0)
  @Infinity [built-in::max|id:0x1234cdef]"""
                    .trimIndent()
            )
    }

    @Test
    fun motionSpec_semantics_formatIsUseful() {
        val semanticKey = SemanticKey<Float>("foo")

        val spec =
            MotionSpec(
                effectsDirectionalMotionSpec(semantics = listOf(semanticKey with 42f)) {
                    fixedValue(0f, value = 1f, semantics = listOf(semanticKey with 43f))
                }
            )

        assertThat(formatForTest(spec.toDebugString()))
            .isEqualTo(
                """
unidirectional:
  @-Infinity [built-in::min|id:0x1234cdef]
    Fixed(value=0.0)
      foo[id:0x1234cdef]=42.0
  @0.0 [id:0x1234cdef] spring=1600.0/1.0
    Fixed(value=1.0)
      foo[id:0x1234cdef]=43.0
  @Infinity [built-in::max|id:0x1234cdef]"""
                    .trimIndent()
            )
    }

    @Test
    fun motionSpec_segmentHandlers_formatIsUseful() {
        val key1 = BreakpointKey("1")
        val key2 = BreakpointKey("2")
        val spec =
            MotionSpec(
                effectsDirectionalMotionSpec {
                    fixedValue(0f, value = 1f, key = key1)
                    fixedValue(2f, value = 2f, key = key1)
                },
                segmentHandlers =
                    mapOf(
                        SegmentKey(key1, key2, InputDirection.Max) to
                            PreventDirectionChangeWithinCurrentSegment,
                        SegmentKey(key1, key2, InputDirection.Min) to
                            PreventDirectionChangeWithinCurrentSegment,
                    ),
            )

        assertThat(formatForTest(spec.toDebugString()))
            .isEqualTo(
                """
unidirectional:
  @-Infinity [built-in::min|id:0x1234cdef]
    Fixed(value=0.0)
  @0.0 [1|id:0x1234cdef] spring=1600.0/1.0
    Fixed(value=1.0)
  @2.0 [1|id:0x1234cdef] spring=1600.0/1.0
    Fixed(value=2.0)
  @Infinity [built-in::max|id:0x1234cdef]
segmentHandlers:
  1|id:0x1234cdef >> 2|id:0x1234cdef
  1|id:0x1234cdef << 2|id:0x1234cdef"""
                    .trimIndent()
            )
    }

    companion object {
        private val idMatcher = Regex("id:0x[0-9a-f]{8}")

        fun formatForTest(debugString: String) =
            debugString.replace(idMatcher, "id:0x1234cdef").trim()
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -254,6 +254,17 @@ class DirectionalBuilderImplTest {
        assertThat(result).semantics().withKey(S2).containsExactly("AAA", "BBB", "BBB").inOrder()
    }

    @Test
    fun directionalSpec_semantics_lateCompletedSegmentsRetainSemantics() {
        val result =
            directionalMotionSpec(Spring, semantics = listOf(S1 with "One")) {
                targetFromCurrent(breakpoint = 0f, to = 10f, semantics = listOf(S1 with "Two"))
                identity(breakpoint = 1f, semantics = listOf(S1 with "Three"))
            }
        assertThat(result).mappings().hasSize(3)
        assertThat(result).semantics().withKey(S1).containsExactly("One", "Two", "Three").inOrder()
    }

    @Test
    fun builderContext_spatialDirectionalMotionSpec_defaultsToSpatialSpringAndIdentityMapping() {
        val context = FakeMotionSpecBuilderContext.Default
Loading