1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.animation 18 19 import android.animation.AnimatorSet 20 import android.graphics.drawable.GradientDrawable 21 import android.platform.test.annotations.MotionTest 22 import android.view.ViewGroup 23 import android.widget.FrameLayout 24 import androidx.test.ext.junit.rules.ActivityScenarioRule 25 import androidx.test.ext.junit.runners.AndroidJUnit4 26 import androidx.test.filters.SmallTest 27 import com.android.systemui.SysuiTestCase 28 import com.android.systemui.activity.EmptyTestActivity 29 import com.android.systemui.concurrency.fakeExecutor 30 import com.android.systemui.kosmos.Kosmos 31 import org.junit.Rule 32 import org.junit.Test 33 import org.junit.runner.RunWith 34 import platform.test.motion.MotionTestRule 35 import platform.test.motion.RecordedMotion 36 import platform.test.motion.view.AnimationSampling.Companion.evenlySampled 37 import platform.test.motion.view.DrawableFeatureCaptures 38 import platform.test.motion.view.ViewRecordingSpec.Companion.captureWithoutScreenshot 39 import platform.test.motion.view.ViewToolkit 40 import platform.test.motion.view.record 41 import platform.test.screenshot.DeviceEmulationRule 42 import platform.test.screenshot.DeviceEmulationSpec 43 import platform.test.screenshot.DisplaySpec 44 import platform.test.screenshot.GoldenPathManager 45 import platform.test.screenshot.PathConfig 46 47 @SmallTest 48 @MotionTest 49 @RunWith(AndroidJUnit4::class) 50 class TransitionAnimatorTest : SysuiTestCase() { 51 companion object { 52 private const val GOLDENS_PATH = "frameworks/base/packages/SystemUI/tests/goldens" 53 54 private val emulationSpec = 55 DeviceEmulationSpec( 56 DisplaySpec( 57 "phone", 58 width = 320, 59 height = 690, 60 densityDpi = 160, 61 ) 62 ) 63 } 64 65 private val kosmos = Kosmos() 66 private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig()) 67 private val transitionAnimator = 68 TransitionAnimator( 69 kosmos.fakeExecutor, 70 ActivityTransitionAnimator.TIMINGS, 71 ActivityTransitionAnimator.INTERPOLATORS 72 ) 73 74 @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(emulationSpec) 75 @get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java) 76 @get:Rule(order = 2) 77 val motionRule = MotionTestRule(ViewToolkit { activityRule.scenario }, pathManager) 78 79 @Test 80 fun backgroundAnimation_whenLaunching() { 81 val backgroundLayer = GradientDrawable().apply { alpha = 0 } 82 val animator = setUpTest(backgroundLayer, isLaunching = true) 83 84 val recordedMotion = recordMotion(backgroundLayer, animator) 85 86 motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden() 87 } 88 89 @Test 90 fun backgroundAnimation_whenReturning() { 91 val backgroundLayer = GradientDrawable().apply { alpha = 0 } 92 val animator = setUpTest(backgroundLayer, isLaunching = false) 93 94 val recordedMotion = recordMotion(backgroundLayer, animator) 95 96 motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden() 97 } 98 99 @Test 100 fun backgroundAnimationWithoutFade_whenLaunching() { 101 val backgroundLayer = GradientDrawable().apply { alpha = 0 } 102 val animator = 103 setUpTest(backgroundLayer, isLaunching = true, fadeWindowBackgroundLayer = false) 104 105 val recordedMotion = recordMotion(backgroundLayer, animator) 106 107 motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden() 108 } 109 110 @Test 111 fun backgroundAnimationWithoutFade_whenReturning() { 112 val backgroundLayer = GradientDrawable().apply { alpha = 0 } 113 val animator = 114 setUpTest(backgroundLayer, isLaunching = false, fadeWindowBackgroundLayer = false) 115 116 val recordedMotion = recordMotion(backgroundLayer, animator) 117 118 motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden() 119 } 120 121 private fun setUpTest( 122 backgroundLayer: GradientDrawable, 123 isLaunching: Boolean, 124 fadeWindowBackgroundLayer: Boolean = true, 125 ): AnimatorSet { 126 lateinit var transitionContainer: ViewGroup 127 activityRule.scenario.onActivity { activity -> 128 transitionContainer = FrameLayout(activity).apply { setBackgroundColor(0x00FF00) } 129 activity.setContentView(transitionContainer) 130 } 131 waitForIdleSync() 132 133 val controller = TestController(transitionContainer, isLaunching) 134 val animator = 135 transitionAnimator.createAnimator( 136 controller, 137 createEndState(transitionContainer), 138 backgroundLayer, 139 fadeWindowBackgroundLayer 140 ) 141 return AnimatorSet().apply { 142 duration = animator.duration 143 play(animator) 144 } 145 } 146 147 private fun createEndState(container: ViewGroup): TransitionAnimator.State { 148 val containerLocation = IntArray(2) 149 container.getLocationOnScreen(containerLocation) 150 return TransitionAnimator.State( 151 left = containerLocation[0], 152 top = containerLocation[1], 153 right = containerLocation[0] + emulationSpec.display.width, 154 bottom = containerLocation[1] + emulationSpec.display.height, 155 topCornerRadius = 0f, 156 bottomCornerRadius = 0f 157 ) 158 } 159 160 private fun recordMotion( 161 backgroundLayer: GradientDrawable, 162 animator: AnimatorSet 163 ): RecordedMotion { 164 return motionRule.record( 165 animator, 166 backgroundLayer.captureWithoutScreenshot(evenlySampled(20)) { 167 feature(DrawableFeatureCaptures.bounds, "bounds") 168 feature(DrawableFeatureCaptures.cornerRadii, "corner_radii") 169 feature(DrawableFeatureCaptures.alpha, "alpha") 170 } 171 ) 172 } 173 } 174 175 /** 176 * A simple implementation of [TransitionAnimator.Controller] which throws if it is called outside 177 * of the main thread. 178 */ 179 private class TestController( 180 override var transitionContainer: ViewGroup, 181 override val isLaunching: Boolean 182 ) : TransitionAnimator.Controller { createAnimatorStatenull183 override fun createAnimatorState(): TransitionAnimator.State { 184 val containerLocation = IntArray(2) 185 transitionContainer.getLocationOnScreen(containerLocation) 186 return TransitionAnimator.State( 187 left = containerLocation[0] + 100, 188 top = containerLocation[1] + 300, 189 right = containerLocation[0] + 200, 190 bottom = containerLocation[1] + 400, 191 topCornerRadius = 10f, 192 bottomCornerRadius = 20f 193 ) 194 } 195 } 196