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

Commit 4925b3f8 authored by Mike Schneider's avatar Mike Schneider
Browse files

Add `MotionBuilderContext` to encapsulate density and motion scheme.

These are commonly used when creating a concrete spec.

- Provides a `FakeMotionSpecBuilderContext` for tests that do not rely
  on the up to date material tokens, but need a "reasonable" defauly
- Introduce a "mechanics-testing" library, which can be used for tests
  of mechanics clients.

Test: Testing library
Flag: EXEMPT not used yet
Bug: 401500734
Bug: 409930448
Change-Id: I821aa4164095064b71ed3fc2fcda496e22771817
parent 512af2a1
Loading
Loading
Loading
Loading
+100 −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.spec.builder

import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MotionScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import com.android.mechanics.spring.SpringParameters

/**
 * Device / scheme specific context for building motion specs.
 *
 * See go/motion-system.
 */
interface MotionBuilderContext : Density {
    /**
     * Spatial spring tokens.
     *
     * Used for animations that move something on screen, for example the x and y position,
     * rotation, size, rounded corners.
     *
     * See go/motion-system#b99b0d12-e9c8-4605-96dd-e3f17bfe9538
     */
    val spatial: MaterialSprings

    /**
     * Effects spring tokens.
     *
     * Used to animate properties such as color and opacity animations.
     *
     * See go/motion-system#142c8835-7474-4f74-b2eb-e1187051ec1f
     */
    val effects: MaterialSprings

    companion object {
        /** Default threshold for effect springs. */
        const val StableThresholdEffects = 0.01f
        /**
         * Default threshold for spatial springs.
         *
         * Cuts off when remaining oscillations are below 1px
         */
        const val StableThresholdSpatial = 1f
    }
}

/** Material spring tokens, see go/motion-system##63b14c00-d049-4d3e-b8b6-83d8f524a8db for usage. */
data class MaterialSprings(
    val default: SpringParameters,
    val fast: SpringParameters,
    val slow: SpringParameters,
    val stabilityThreshold: Float,
)

/** [MotionBuilderContext] based on the current [Density] and [MotionScheme]. */
@Composable
fun rememberMotionBuilderContext(): MotionBuilderContext {
    val density = LocalDensity.current
    val motionScheme = MaterialTheme.motionScheme
    return remember(density, motionScheme) { ComposeMotionBuilderContext(motionScheme, density) }
}

internal class ComposeMotionBuilderContext(motionScheme: MotionScheme, density: Density) :
    MotionBuilderContext, Density by density {

    override val spatial =
        MaterialSprings(
            SpringParameters(motionScheme.defaultSpatialSpec<Float>()),
            SpringParameters(motionScheme.fastSpatialSpec<Float>()),
            SpringParameters(motionScheme.slowSpatialSpec<Float>()),
            MotionBuilderContext.StableThresholdSpatial,
        )
    override val effects =
        MaterialSprings(
            SpringParameters(motionScheme.defaultEffectsSpec<Float>()),
            SpringParameters(motionScheme.fastEffectsSpec<Float>()),
            SpringParameters(motionScheme.slowEffectsSpec<Float>()),
            MotionBuilderContext.StableThresholdEffects,
        )
}
+36 −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 {
    default_applicable_licenses: ["Android-Apache-2.0"],
    default_team: "trendy_team_motion",
}

android_library {
    name: "mechanics-testing",
    manifest: "AndroidManifest.xml",
    srcs: [
        "src/**/*.kt",
    ],
    static_libs: [
        "//frameworks/libs/systemui/mechanics:mechanics",
        "platform-test-annotations",
        "PlatformMotionTestingCompose",
        "androidx.compose.runtime_runtime",
        "androidx.compose.ui_ui-test-junit4",
        "testables",
        "truth",
    ],
}
+20 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ 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.
  -->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.mechanics.testing">
</manifest>
+54 −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.testing

import androidx.compose.ui.unit.Density
import com.android.mechanics.spec.builder.MaterialSprings
import com.android.mechanics.spec.builder.MotionBuilderContext
import com.android.mechanics.spring.SpringParameters

/**
 * [MotionBuilderContext] implementation for unit tests.
 *
 * Only use when the specifics of the spring parameters do not matter for the test.
 *
 * While the values are copied from the current material motion tokens, this can (and likely will)
 * get out of sync with the material tokens, and is not intended reflect the up-to-date tokens, but
 * provide a stable definitions of "some" spring parameters.
 */
class FakeMotionSpecBuilderContext(density: Float = 1f) :
    MotionBuilderContext, Density by Density(density) {
    override val spatial =
        MaterialSprings(
            SpringParameters(700.0f, 0.9f),
            SpringParameters(1400.0f, 0.9f),
            SpringParameters(300.0f, 0.9f),
            MotionBuilderContext.StableThresholdSpatial,
        )

    override val effects =
        MaterialSprings(
            SpringParameters(1600.0f, 1.0f),
            SpringParameters(3800.0f, 1.0f),
            SpringParameters(800.0f, 1.0f),
            MotionBuilderContext.StableThresholdEffects,
        )

    companion object {
        val Default = FakeMotionSpecBuilderContext()
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ android_test {
        "androidx.compose.foundation_foundation-layout",

        // ":mechanics_tests" dependencies
        "//frameworks/libs/systemui/mechanics:mechanics-testing",
        "androidx.compose.animation_animation-core",
        "platform-test-annotations",
        "PlatformMotionTestingCompose",