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

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

Snap for 13431276 from 10e8d60c to 25Q3-release

Change-Id: I0014e9b0e4f65766db76089f08c4e514ec7ba4a9
parents 0bbe4042 10e8d60c
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -31,6 +31,10 @@ import com.android.mechanics.spring.SpringParameters
 * Device / scheme specific context for building motion specs.
 *
 * See go/motion-system.
 *
 * @see rememberMotionBuilderContext for Compose
 * @see standardViewMotionBuilderContext for Views
 * @see expressiveViewMotionBuilderContext for Views
 */
interface MotionBuilderContext : Density {
    /**
+127 −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.view

import android.content.Context
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
import com.android.mechanics.view.ViewMaterialSprings.Default

/**
 * Creates a [MotionBuilderContext] using the **standard** motion spec.
 *
 * See go/motion-system.
 *
 * @param context The context to derive the density from.
 */
fun standardViewMotionBuilderContext(context: Context): MotionBuilderContext {
    return standardViewMotionBuilderContext(context.resources.displayMetrics.density)
}

/**
 * Creates a [MotionBuilderContext] using the **standard** motion spec.
 *
 * See go/motion-system.
 *
 * @param density The density of the display, as a scaling factor for the dp to px conversion.
 */
fun standardViewMotionBuilderContext(density: Float): MotionBuilderContext {
    return with(ViewMaterialSprings.Default) {
        ViewMotionBuilderContext(Spatial, Effects, Density(density))
    }
}

/**
 * Creates a [MotionBuilderContext] using the **expressive** motion spec.
 *
 * See go/motion-system.
 *
 * @param context The context to derive the density from.
 */
fun expressiveViewMotionBuilderContext(context: Context): MotionBuilderContext {
    return expressiveViewMotionBuilderContext(context.resources.displayMetrics.density)
}

/**
 * Creates a [MotionBuilderContext] using the **expressive** motion spec.
 *
 * See go/motion-system.
 *
 * @param density The density of the display, as a scaling factor for the dp to px conversion.
 */
fun expressiveViewMotionBuilderContext(density: Float): MotionBuilderContext {
    return with(ViewMaterialSprings.Expressive) {
        ViewMotionBuilderContext(Spatial, Effects, Density(density))
    }
}

/**
 * Material motion system spring definitions.
 *
 * See go/motion-system.
 *
 * NOTE: These are only defined here since material spring parameters are not available for View
 * based APIs. There might be a delay in updating these values, should the material tokens be
 * updated in the future.
 *
 * @see rememberMotionBuilderContext for Compose
 */
object ViewMaterialSprings {
    object Default {
        val Spatial =
            MaterialSprings(
                SpringParameters(700.0f, 0.9f),
                SpringParameters(1400.0f, 0.9f),
                SpringParameters(300.0f, 0.9f),
                MotionBuilderContext.StableThresholdSpatial,
            )

        val Effects =
            MaterialSprings(
                SpringParameters(1600.0f, 1.0f),
                SpringParameters(3800.0f, 1.0f),
                SpringParameters(800.0f, 1.0f),
                MotionBuilderContext.StableThresholdEffects,
            )
    }

    object Expressive {
        val Spatial =
            MaterialSprings(
                SpringParameters(380.0f, 0.8f),
                SpringParameters(800.0f, 0.6f),
                SpringParameters(200.0f, 0.8f),
                MotionBuilderContext.StableThresholdSpatial,
            )

        val Effects =
            MaterialSprings(
                SpringParameters(1600.0f, 1.0f),
                SpringParameters(3800.0f, 1.0f),
                SpringParameters(800.0f, 1.0f),
                MotionBuilderContext.StableThresholdEffects,
            )
    }
}

internal class ViewMotionBuilderContext(
    override val spatial: MaterialSprings,
    override val effects: MaterialSprings,
    density: Density,
) : MotionBuilderContext, Density by density
+77 −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.view

import android.content.Context
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MotionScheme
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.mechanics.spec.builder.MotionBuilderContext
import com.android.mechanics.spec.builder.rememberMotionBuilderContext
import com.google.common.truth.Truth
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ViewMotionBuilderContextTest {

    @get:Rule(order = 0) val rule = createComposeRule()

    @Test
    fun materialSprings_standardScheme_matchesComposeDefinition() {
        lateinit var viewContext: Context
        lateinit var composeReference: MotionBuilderContext

        rule.setContent {
            viewContext = LocalContext.current
            MaterialTheme(motionScheme = MotionScheme.standard()) {
                composeReference = rememberMotionBuilderContext()
            }
        }

        val underTest = standardViewMotionBuilderContext(viewContext)

        Truth.assertThat(underTest.density).isEqualTo(composeReference.density)
        Truth.assertThat(underTest.spatial).isEqualTo(composeReference.spatial)
        Truth.assertThat(underTest.effects).isEqualTo(composeReference.effects)
    }

    @Test
    fun materialSprings_expressiveScheme_matchesComposeDefinition() {
        lateinit var viewContext: Context
        lateinit var composeReference: MotionBuilderContext

        rule.setContent {
            viewContext = LocalContext.current
            MaterialTheme(motionScheme = MotionScheme.expressive()) {
                composeReference = rememberMotionBuilderContext()
            }
        }

        val underTest = expressiveViewMotionBuilderContext(viewContext)

        Truth.assertThat(underTest.density).isEqualTo(composeReference.density)
        Truth.assertThat(underTest.spatial).isEqualTo(composeReference.spatial)
        Truth.assertThat(underTest.effects).isEqualTo(composeReference.effects)
    }
}
+19 −25
Original line number Diff line number Diff line
@@ -22,41 +22,38 @@ package com.google.android.torus.core.power
 */
class FpsThrottler {
    companion object {
        private const val NANO_TO_MILLIS = 1 / 1E6
        const val NANO_TO_MILLIS = 1 / 1E6

        const val FPS_120 = 120f
        const val FPS_60 = 60f
        const val FPS_30 = 30f
        const val FPS_18 = 18f

        @Deprecated(message = "Use FPS_60 instead.")
        const val HIGH_FPS = 60f
        @Deprecated(message = "Use FPS_30 instead.")
        const val MED_FPS = 30f
        @Deprecated(message = "Use FPS_18 instead.")
        const val LOW_FPS = 18f
        @Deprecated(message = "Use FPS_60 instead.") const val HIGH_FPS = 60f
        @Deprecated(message = "Use FPS_30 instead.") const val MED_FPS = 30f
        @Deprecated(message = "Use FPS_18 instead.") const val LOW_FPS = 18f

        /** Small tolerance (ms) for float precision in frame timing. */
        const val TOLERANCE_MILLIS = 1L
    }

    private var fps: Float = FPS_60

    @Volatile
    private var frameTimeMillis: Double = 1000.0 / fps.toDouble()
    @Volatile private var frameTimeMillis: Double = 1000.0 / fps.toDouble()
    private var lastFrameTimeNanos: Long = -1

    @Volatile
    private var continuousRenderingMode: Boolean = true
    @Volatile private var continuousRenderingMode: Boolean = true

    @Volatile
    private var requestRendering: Boolean = false
    @Volatile private var requestRendering: Boolean = false

    private fun updateFrameTime() {
        frameTimeMillis = 1000.0 / fps.toDouble()
    }

    /**
     * If [fps] is non-zero, update the requested FPS and calculate the frame time
     * for the requested FPS. Otherwise disable continuous rendering (on demand rendering)
     * without changing the frame rate.
     * If [fps] is non-zero, update the requested FPS and calculate the frame time for the requested
     * FPS. Otherwise disable continuous rendering (on demand rendering) without changing the frame
     * rate.
     *
     * @param fps The requested FPS value.
     */
@@ -86,13 +83,11 @@ class FpsThrottler {
    }

    /**
     * Calculates whether we can render the next frame. In continuous mode return true only
     * if enough time has passed since the last render to maintain requested FPS.
     * In on demand mode, return true only if [requestRendering] was called to render
     * the next frame.
     * Calculates whether we can render the next frame. In continuous mode return true only if
     * enough time has passed since the last render to maintain requested FPS. In on demand mode,
     * return true only if [requestRendering] was called to render the next frame.
     *
     * @param frameTimeNanos The time in nanoseconds when the current frame started.
     *
     * @return true if we can render the next frame.
     */
    fun canRender(frameTimeNanos: Long): Boolean {
@@ -102,7 +97,7 @@ class FpsThrottler {
                true
            } else {
                val deltaMillis = (frameTimeNanos - lastFrameTimeNanos) * NANO_TO_MILLIS
                return (deltaMillis >= frameTimeMillis) && (fps > 0f)
                return (deltaMillis >= frameTimeMillis - TOLERANCE_MILLIS) && (fps > 0f)
            }
        } else {
            // on demand rendering
@@ -120,7 +115,6 @@ class FpsThrottler {
     * @param frameTimeNanos The time in nanoseconds when the current frame started.
     * @param onRenderPermitted The client delegate to dispatch if rendering is permitted at this
     *   time.
     *
     * @return true if a frame is permitted and then actually rendered.
     */
    fun tryRender(frameTimeNanos: Long, onRenderPermitted: () -> Boolean): Boolean {
+44 −48
Original line number Diff line number Diff line
@@ -31,9 +31,9 @@ import com.google.android.torus.core.wallpaper.LiveWallpaper
import java.io.PrintWriter

/**
 * Class that implements [TorusEngine] using Canvas and can be used in a [LiveWallpaper]. This
 * class also inherits from [LiveWallpaper.LiveWallpaperConnector] which allows to do some calls
 * related to Live Wallpapers, like the method [isPreview] or [notifyWallpaperColorsChanged].
 * Class that implements [TorusEngine] using Canvas and can be used in a [LiveWallpaper]. This class
 * also inherits from [LiveWallpaper.LiveWallpaperConnector] which allows to do some calls related
 * to Live Wallpapers, like the method [isPreview] or [notifyWallpaperColorsChanged].
 *
 * By default it won't start [startUpdateLoop]. To run animations and update logic per frame, call
 * [startUpdateLoop] and [stopUpdateLoop] when it's no longer needed.
@@ -46,22 +46,22 @@ abstract class CanvasWallpaperEngine(

    /**
     * Defines if the surface should be hardware accelerated or not. If you are using
     * [RuntimeShader], this value should be set to true. When setting it to true, some
     * functions might not be supported. Please refer to the documentation:
     * [RuntimeShader], this value should be set to true. When setting it to true, some functions
     * might not be supported. Please refer to the documentation:
     * https://developer.android.com/guide/topics/graphics/hardware-accel#unsupported
     */
    private val hardwareAccelerated: Boolean = false,
) : LiveWallpaper.LiveWallpaperConnector(), TorusEngine {

    private val choreographer = Choreographer.getInstance()
    private val timeController = TimeController().also {
        it.resetDeltaTime(SystemClock.uptimeMillis())
    }
    private val timeController =
        TimeController().also { it.resetDeltaTime(SystemClock.uptimeMillis()) }
    private val frameScheduler = FrameCallback()
    private val fpsThrottler = FpsThrottler()

    protected var screenSize = Size(0, 0)
        private set

    private var resizeCalled: Boolean = false

    private var isWallpaperEngineVisible = false
@@ -110,8 +110,8 @@ abstract class CanvasWallpaperEngine(
     * update logic and render in this loop.
     *
     * @param deltaMillis The time in millis since the last time [onUpdate] was called.
     * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
     * in the [System.nanoTime] timebase.
     * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, in the
     *   [System.nanoTime] timebase.
     */
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    open fun onUpdate(deltaMillis: Long, frameTimeNanos: Long) {
@@ -130,9 +130,10 @@ abstract class CanvasWallpaperEngine(
    }

    final override fun create(isFirstActiveInstance: Boolean) {
        screenSize = Size(
        screenSize =
            Size(
                getCurrentSurfaceHolder().surfaceFrame.width(),
            getCurrentSurfaceHolder().surfaceFrame.height()
                getCurrentSurfaceHolder().surfaceFrame.height(),
            )

        onCreate(isFirstActiveInstance)
@@ -141,8 +142,10 @@ abstract class CanvasWallpaperEngine(

        if (shouldInvokeResume) {
            Log.e(
                TAG, "Force invoke resume. onVisibilityChanged must have been called" +
                        "before onCreate.")
                TAG,
                "Force invoke resume. onVisibilityChanged must have been called" +
                    "before onCreate.",
            )
            resume()
            shouldInvokeResume = false
        }
@@ -151,8 +154,10 @@ abstract class CanvasWallpaperEngine(
    final override fun pause() {
        if (!isCreated) {
            Log.e(
                TAG, "Engine is not yet created but pause is called. Set a flag to invoke" +
                        " resume on next create.")
                TAG,
                "Engine is not yet created but pause is called. Set a flag to invoke" +
                    " resume on next create.",
            )
            shouldInvokeResume = true
            return
        }
@@ -166,8 +171,10 @@ abstract class CanvasWallpaperEngine(
    final override fun resume() {
        if (!isCreated) {
            Log.e(
                TAG, "Engine is not yet created but resume is called. Set a flag to " +
                        "invoke resume on next create.")
                TAG,
                "Engine is not yet created but resume is called. Set a flag to " +
                    "invoke resume on next create.",
            )
            shouldInvokeResume = true
            return
        }
@@ -200,7 +207,6 @@ abstract class CanvasWallpaperEngine(
     * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, in the
     *   [System.nanoTime] timebase.
     * @param onRender The callback triggered when the canvas is ready for render.
     *
     * @return Whether it is rendered.
     */
    fun renderWithFpsLimit(frameTimeNanos: Long, onRender: (canvas: Canvas) -> Unit): Boolean {
@@ -215,16 +221,13 @@ abstract class CanvasWallpaperEngine(
            return renderWithFpsLimit(frameTimeNanos, onRender)
        }

        return fpsThrottler.tryRender(frameTimeNanos) {
            renderToCanvas(onRender)
        }
        return fpsThrottler.tryRender(frameTimeNanos) { renderToCanvas(onRender) }
    }

    /**
     * Renders to canvas.
     *
     * @param onRender The callback triggered when the canvas is ready for render.
     *
     * @return Whether it is rendered.
     */
    fun render(onRender: (canvas: Canvas) -> Unit): Boolean {
@@ -250,9 +253,7 @@ abstract class CanvasWallpaperEngine(
        fpsThrottler.updateFps(fps)
    }

    /**
     * Starts the update loop.
     */
    /** Starts the update loop. */
    protected fun startUpdateLoop() {
        if (!frameScheduler.running) {
            frameScheduler.running = true
@@ -260,9 +261,7 @@ abstract class CanvasWallpaperEngine(
        }
    }

    /**
     * Stops the update loop.
     */
    /** Stops the update loop. */
    protected fun stopUpdateLoop() {
        if (frameScheduler.running) {
            frameScheduler.running = false
@@ -276,14 +275,14 @@ abstract class CanvasWallpaperEngine(
        var canvas: Canvas? = null

        try {
            canvas = if (hardwareAccelerated) {
            canvas =
                if (hardwareAccelerated) {
                    surfaceHolder.lockHardwareCanvas()
                } else {
                    surfaceHolder.lockCanvas()
                } ?: return false

            onRender(canvas)

        } catch (e: java.lang.Exception) {
            Log.e("canvas_exception", "canvas exception", e)
        } finally {
@@ -294,12 +293,9 @@ abstract class CanvasWallpaperEngine(
        return true
    }

    private fun getCurrentSurfaceHolder(): SurfaceHolder =
        getEngineSurfaceHolder() ?: defaultHolder
    private fun getCurrentSurfaceHolder(): SurfaceHolder = getEngineSurfaceHolder() ?: defaultHolder

    /**
     * Implementation of [Choreographer.FrameCallback] which triggers [onUpdate].
     */
    /** Implementation of [Choreographer.FrameCallback] which triggers [onUpdate]. */
    inner class FrameCallback : Choreographer.FrameCallback {
        internal var running: Boolean = false

Loading