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

Commit c90fcf26 authored by Yuichiro Hanada's avatar Yuichiro Hanada
Browse files

Move the animator for the minimization to WMShell

to share the animator with the minimize button.

Flag: EXEMPT refactoring
Bug: 369349585
Test: manual
Change-Id: I42325913313a85e1086ab6bc4151f60cb3bc4605
parent 972531f5
Loading
Loading
Loading
Loading
+10 −36
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ import android.window.TransitionInfo.Change
import androidx.core.animation.addListener
import com.android.app.animation.Interpolators
import com.android.quickstep.RemoteRunnable
import com.android.wm.shell.shared.animation.MinimizeAnimator
import com.android.wm.shell.shared.animation.WindowAnimator
import java.util.concurrent.Executor

/**
@@ -77,7 +79,13 @@ class DesktopAppLaunchTransition(private val context: Context, private val mainE
        val launchAnimator =
            createLaunchAnimator(getLaunchChange(info), transaction, finishCallback)
        val minimizeChange = getMinimizeChange(info) ?: return listOf(launchAnimator)
        val minimizeAnimator = createMinimizeAnimator(minimizeChange, transaction, finishCallback)
        val minimizeAnimator =
            MinimizeAnimator.create(
                context.resources.displayMetrics,
                minimizeChange,
                transaction,
                finishCallback,
            )
        return listOf(launchAnimator, minimizeAnimator)
    }

@@ -96,7 +104,7 @@ class DesktopAppLaunchTransition(private val context: Context, private val mainE
    ): Animator {
        val boundsAnimator =
            WindowAnimator.createBoundsAnimator(
                context,
                context.resources.displayMetrics,
                launchBoundsAnimationDef,
                change,
                transaction,
@@ -115,32 +123,6 @@ class DesktopAppLaunchTransition(private val context: Context, private val mainE
        }
    }

    private fun createMinimizeAnimator(
        change: Change,
        transaction: Transaction,
        onAnimFinish: (Animator) -> Unit,
    ): Animator {
        val boundsAnimator =
            WindowAnimator.createBoundsAnimator(
                context,
                minimizeBoundsAnimationDef,
                change,
                transaction,
            )
        val alphaAnimator =
            ValueAnimator.ofFloat(1f, 0f).apply {
                duration = MINIMIZE_ANIM_ALPHA_DURATION_MS
                interpolator = Interpolators.LINEAR
                addUpdateListener { animation ->
                    transaction.setAlpha(change.leash, animation.animatedValue as Float).apply()
                }
            }
        return AnimatorSet().apply {
            playTogether(boundsAnimator, alphaAnimator)
            addListener(onEnd = { animation -> onAnimFinish(animation) })
        }
    }

    companion object {
        private val LAUNCH_CHANGE_MODES = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)

@@ -154,13 +136,5 @@ class DesktopAppLaunchTransition(private val context: Context, private val mainE
                startScale = 0.97f,
                interpolator = Interpolators.STANDARD_DECELERATE,
            )

        private val minimizeBoundsAnimationDef =
            WindowAnimator.BoundsAnimationParams(
                durationMs = 200,
                endOffsetYDp = 12f,
                endScale = 0.97f,
                interpolator = Interpolators.STANDARD_ACCELERATE,
            )
    }
}
+0 −100
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.launcher3.desktop

import android.animation.RectEvaluator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
import android.util.TypedValue
import android.view.SurfaceControl
import android.view.animation.Interpolator
import android.window.TransitionInfo

/** Creates animations that can be applied to windows/surfaces. */
object WindowAnimator {

    /** Parameters defining a window bounds animation. */
    data class BoundsAnimationParams(
        val durationMs: Long,
        val startOffsetYDp: Float = 0f,
        val endOffsetYDp: Float = 0f,
        val startScale: Float = 1f,
        val endScale: Float = 1f,
        val interpolator: Interpolator,
    )

    /**
     * Creates an animator to reposition and scale the bounds of the leash of the given change.
     *
     * @param boundsAnimDef the parameters for the animation itself (duration, scale, position)
     * @param change the change to which the animation should be applied
     * @param transaction the transaction to apply the animation to
     */
    fun createBoundsAnimator(
        context: Context,
        boundsAnimDef: BoundsAnimationParams,
        change: TransitionInfo.Change,
        transaction: SurfaceControl.Transaction,
    ): ValueAnimator {
        val startBounds =
            createBounds(
                context,
                change.startAbsBounds,
                boundsAnimDef.startScale,
                boundsAnimDef.startOffsetYDp,
            )
        val leash = change.leash
        val endBounds =
            createBounds(
                context,
                change.startAbsBounds,
                boundsAnimDef.endScale,
                boundsAnimDef.endOffsetYDp,
            )
        return ValueAnimator.ofObject(RectEvaluator(), startBounds, endBounds).apply {
            duration = boundsAnimDef.durationMs
            interpolator = boundsAnimDef.interpolator
            addUpdateListener { animation ->
                val animBounds = animation.animatedValue as Rect
                val animScale = 1 - (1 - boundsAnimDef.endScale) * animation.animatedFraction
                transaction
                    .setPosition(leash, animBounds.left.toFloat(), animBounds.top.toFloat())
                    .setScale(leash, animScale, animScale)
                    .apply()
            }
        }
    }

    private fun createBounds(context: Context, origBounds: Rect, scale: Float, offsetYDp: Float) =
        Rect(origBounds).apply {
            check(scale in 0.0..1.0)
            // Scale the  bounds down with an anchor in the center
            inset(
                (origBounds.width().toFloat() * (1 - scale) / 2).toInt(),
                (origBounds.height().toFloat() * (1 - scale) / 2).toInt(),
            )
            val offsetYPx =
                TypedValue.applyDimension(
                        TypedValue.COMPLEX_UNIT_DIP,
                        offsetYDp,
                        context.resources.displayMetrics,
                    )
                    .toInt()
            offset(/* dx= */ 0, offsetYPx)
        }
}
+0 −139
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.quickstep.desktop

import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Resources
import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.SurfaceControl
import android.window.TransitionInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import com.android.app.animation.Interpolators
import com.android.launcher3.desktop.WindowAnimator
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
class WindowAnimatorTest {

    private val context = mock<Context>()
    private val resources = mock<Resources>()
    private val transaction = mock<SurfaceControl.Transaction>()
    private val change = mock<TransitionInfo.Change>()
    private val leash = mock<SurfaceControl>()

    private val displayMetrics = DisplayMetrics().apply { density = 1f }

    @Before
    fun setup() {
        whenever(context.resources).thenReturn(resources)
        whenever(resources.displayMetrics).thenReturn(displayMetrics)
        whenever(change.leash).thenReturn(leash)
        whenever(change.startAbsBounds).thenReturn(START_BOUNDS)
        whenever(transaction.setPosition(any(), anyFloat(), anyFloat())).thenReturn(transaction)
        whenever(transaction.setScale(any(), anyFloat(), anyFloat())).thenReturn(transaction)
    }

    @Test
    fun createBoundsAnimator_returnsCorrectDefaultAnimatorParams() = runOnUiThread {
        val boundsAnimParams =
            WindowAnimator.BoundsAnimationParams(
                durationMs = 100L,
                interpolator = Interpolators.STANDARD_ACCELERATE,
            )

        val valueAnimator =
            WindowAnimator.createBoundsAnimator(context, boundsAnimParams, change, transaction)

        assertThat(valueAnimator.duration).isEqualTo(100L)
        assertThat(valueAnimator.interpolator).isEqualTo(Interpolators.STANDARD_ACCELERATE)
        assertStartAndEndBounds(valueAnimator, startBounds = START_BOUNDS, endBounds = START_BOUNDS)
    }

    @Test
    fun createBoundsAnimator_startScaleAndOffset_returnsCorrectBounds() = runOnUiThread {
        val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400)
        whenever(change.startAbsBounds).thenReturn(bounds)
        val boundsAnimParams =
            WindowAnimator.BoundsAnimationParams(
                durationMs = 100L,
                startOffsetYDp = 10f,
                startScale = 0.5f,
                interpolator = Interpolators.STANDARD_ACCELERATE,
            )

        val valueAnimator =
            WindowAnimator.createBoundsAnimator(context, boundsAnimParams, change, transaction)

        assertStartAndEndBounds(
            valueAnimator,
            startBounds =
                Rect(/* left= */ 150, /* top= */ 260, /* right= */ 250, /* bottom= */ 360),
            endBounds = bounds,
        )
    }

    @Test
    fun createBoundsAnimator_endScaleAndOffset_returnsCorrectBounds() = runOnUiThread {
        val bounds = Rect(/* left= */ 100, /* top= */ 200, /* right= */ 300, /* bottom= */ 400)
        whenever(change.startAbsBounds).thenReturn(bounds)
        val boundsAnimParams =
            WindowAnimator.BoundsAnimationParams(
                durationMs = 100L,
                endOffsetYDp = 10f,
                endScale = 0.5f,
                interpolator = Interpolators.STANDARD_ACCELERATE,
            )

        val valueAnimator =
            WindowAnimator.createBoundsAnimator(context, boundsAnimParams, change, transaction)

        assertStartAndEndBounds(
            valueAnimator,
            startBounds = bounds,
            endBounds = Rect(/* left= */ 150, /* top= */ 260, /* right= */ 250, /* bottom= */ 360),
        )
    }

    private fun assertStartAndEndBounds(
        valueAnimator: ValueAnimator,
        startBounds: Rect,
        endBounds: Rect,
    ) {
        valueAnimator.start()
        valueAnimator.animatedValue
        assertThat(valueAnimator.animatedValue).isEqualTo(startBounds)
        valueAnimator.end()
        assertThat(valueAnimator.animatedValue).isEqualTo(endBounds)
    }

    companion object {
        private val START_BOUNDS =
            Rect(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40)
    }
}