• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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