1 /* 2 * Copyright (C) 2021 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.os.HandlerThread 20 import android.platform.test.annotations.DisableFlags 21 import android.platform.test.annotations.EnableFlags 22 import android.testing.TestableLooper 23 import android.view.View 24 import android.widget.FrameLayout 25 import androidx.test.ext.junit.runners.AndroidJUnit4 26 import androidx.test.filters.SmallTest 27 import com.android.internal.jank.InteractionJankMonitor 28 import com.android.systemui.Flags 29 import com.android.systemui.SysuiTestCase 30 import com.android.systemui.animation.view.LaunchableFrameLayout 31 import com.google.common.truth.Truth.assertThat 32 import org.junit.Assert.assertThrows 33 import org.junit.Before 34 import org.junit.Test 35 import org.junit.runner.RunWith 36 37 @SmallTest 38 @RunWith(AndroidJUnit4::class) 39 @TestableLooper.RunWithLooper 40 class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() { 41 companion object { 42 private const val LAUNCH_CUJ = 0 43 private const val RETURN_CUJ = 1 44 } 45 46 private val interactionJankMonitor = FakeInteractionJankMonitor() 47 private lateinit var transitionRegistry: FakeViewTransitionRegistry 48 private lateinit var transitioningView: View 49 50 @Before setupnull51 fun setup() { 52 transitioningView = LaunchableFrameLayout(mContext) 53 transitionRegistry = FakeViewTransitionRegistry() 54 } 55 56 @Test animatingOrphanViewDoesNotCrashnull57 fun animatingOrphanViewDoesNotCrash() { 58 val state = TransitionAnimator.State(top = 0, bottom = 0, left = 0, right = 0) 59 60 val controller = GhostedViewTransitionAnimatorController(LaunchableFrameLayout(mContext)) 61 controller.onIntentStarted(willAnimate = true) 62 controller.onTransitionAnimationStart(isExpandingFullyAbove = true) 63 controller.onTransitionAnimationProgress(state, progress = 0f, linearProgress = 0f) 64 controller.onTransitionAnimationEnd(isExpandingFullyAbove = true) 65 } 66 67 @Test creatingControllerFromNormalViewThrowsnull68 fun creatingControllerFromNormalViewThrows() { 69 assertThrows(IllegalArgumentException::class.java) { 70 GhostedViewTransitionAnimatorController(FrameLayout(mContext)) 71 } 72 } 73 74 @Test cujsAreLoggedCorrectlynull75 fun cujsAreLoggedCorrectly() { 76 val parent = FrameLayout(mContext) 77 78 val launchView = LaunchableFrameLayout(mContext) 79 parent.addView((launchView)) 80 val launchController = 81 GhostedViewTransitionAnimatorController( 82 launchView, 83 launchCujType = LAUNCH_CUJ, 84 returnCujType = RETURN_CUJ, 85 interactionJankMonitor = interactionJankMonitor 86 ) 87 launchController.onTransitionAnimationStart(isExpandingFullyAbove = true) 88 assertThat(interactionJankMonitor.ongoing).containsExactly(LAUNCH_CUJ) 89 launchController.onTransitionAnimationEnd(isExpandingFullyAbove = true) 90 assertThat(interactionJankMonitor.ongoing).isEmpty() 91 assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ) 92 93 val returnView = LaunchableFrameLayout(mContext) 94 parent.addView((returnView)) 95 val returnController = 96 object : GhostedViewTransitionAnimatorController( 97 returnView, 98 launchCujType = LAUNCH_CUJ, 99 returnCujType = RETURN_CUJ, 100 interactionJankMonitor = interactionJankMonitor 101 ) { 102 override val isLaunching = false 103 } 104 returnController.onTransitionAnimationStart(isExpandingFullyAbove = true) 105 assertThat(interactionJankMonitor.ongoing).containsExactly(RETURN_CUJ) 106 returnController.onTransitionAnimationEnd(isExpandingFullyAbove = true) 107 assertThat(interactionJankMonitor.ongoing).isEmpty() 108 assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ, RETURN_CUJ) 109 } 110 111 @EnableFlags(Flags.FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB) 112 @Test testViewsAreRegisteredInTransitionRegistrynull113 fun testViewsAreRegisteredInTransitionRegistry() { 114 GhostedViewTransitionAnimatorController( 115 transitioningView = transitioningView, 116 transitionRegistry = transitionRegistry 117 ) 118 assertThat(transitionRegistry.registry).isNotEmpty() 119 } 120 121 @DisableFlags(Flags.FLAG_DECOUPLE_VIEW_CONTROLLER_IN_ANIMLIB) 122 @Test testNotUseRegistryIfDecouplingFlagDisablednull123 fun testNotUseRegistryIfDecouplingFlagDisabled() { 124 GhostedViewTransitionAnimatorController( 125 transitioningView = transitioningView, 126 transitionRegistry = transitionRegistry 127 ) 128 assertThat(transitionRegistry.registry).isEmpty() 129 } 130 131 /** 132 * A fake implementation of [InteractionJankMonitor] which stores ongoing and finished CUJs and 133 * allows inspection. 134 */ 135 private class FakeInteractionJankMonitor : InteractionJankMonitor( 136 HandlerThread("testThread") 137 ) { 138 val ongoing: MutableSet<Int> = mutableSetOf() 139 val finished: MutableSet<Int> = mutableSetOf() 140 beginnull141 override fun begin(v: View?, cujType: Int): Boolean { 142 ongoing.add(cujType) 143 return true 144 } 145 endnull146 override fun end(cujType: Int): Boolean { 147 ongoing.remove(cujType) 148 finished.add(cujType) 149 return true 150 } 151 } 152 153 private class FakeViewTransitionRegistry : IViewTransitionRegistry { 154 155 val registry = mutableMapOf<ViewTransitionToken, View>() 156 val token = ViewTransitionToken() 157 registernull158 override fun register(view: View): ViewTransitionToken { 159 registry[token] = view 160 view.setTag(R.id.tag_view_transition_token, token) 161 return token 162 } 163 unregisternull164 override fun unregister(token: ViewTransitionToken) { 165 registry.remove(token)?.setTag(R.id.tag_view_transition_token, null) 166 } 167 getViewnull168 override fun getView(token: ViewTransitionToken): View? { 169 return registry[token] 170 } 171 getViewTokennull172 override fun getViewToken(view: View): ViewTransitionToken? { 173 return view.getTag(R.id.tag_view_transition_token) as? ViewTransitionToken 174 } 175 onRegistryUpdatenull176 override fun onRegistryUpdate() { 177 //empty 178 } 179 } 180 } 181