1 /* 2 * Copyright (C) 2022 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.media.controls.ui.animation 18 19 import android.animation.Animator 20 import android.testing.TestableLooper 21 import androidx.test.ext.junit.runners.AndroidJUnit4 22 import androidx.test.filters.SmallTest 23 import com.android.systemui.SysuiTestCase 24 import junit.framework.Assert.fail 25 import org.junit.After 26 import org.junit.Before 27 import org.junit.Rule 28 import org.junit.Test 29 import org.junit.runner.RunWith 30 import org.mockito.Mock 31 import org.mockito.Mockito.mock 32 import org.mockito.Mockito.never 33 import org.mockito.Mockito.times 34 import org.mockito.Mockito.verify 35 import org.mockito.Mockito.`when` as whenever 36 import org.mockito.junit.MockitoJUnit 37 38 @SmallTest 39 @RunWith(AndroidJUnit4::class) 40 @TestableLooper.RunWithLooper(setAsMainLooper = true) 41 class MetadataAnimationHandlerTest : SysuiTestCase() { 42 43 private interface Callback : () -> Unit 44 private lateinit var handler: MetadataAnimationHandler 45 46 @Mock private lateinit var enterAnimator: Animator 47 @Mock private lateinit var exitAnimator: Animator 48 @Mock private lateinit var postExitCB: Callback 49 @Mock private lateinit var postEnterCB: Callback 50 51 @JvmField @Rule val mockito = MockitoJUnit.rule() 52 53 @Before setUpnull54 fun setUp() { 55 handler = MetadataAnimationHandler(exitAnimator, enterAnimator) 56 } 57 tearDownnull58 @After fun tearDown() {} 59 60 @Test firstBind_startsAnimationSetnull61 fun firstBind_startsAnimationSet() { 62 val cb = { fail("Unexpected callback") } 63 handler.setNext("data-1", cb, cb) 64 65 verify(exitAnimator).start() 66 } 67 68 @Test executeAnimationEnd_runsCallacksnull69 fun executeAnimationEnd_runsCallacks() { 70 // We expect this first call to only start the exit animator 71 handler.setNext("data-1", postExitCB, postEnterCB) 72 verify(exitAnimator, times(1)).start() 73 verify(enterAnimator, never()).start() 74 verify(postExitCB, never()).invoke() 75 verify(postEnterCB, never()).invoke() 76 77 // After the exit animator completes, 78 // the exit cb should run, and enter animation should start 79 handler.onAnimationEnd(exitAnimator) 80 verify(exitAnimator, times(1)).start() 81 verify(enterAnimator, times(1)).start() 82 verify(postExitCB, times(1)).invoke() 83 verify(postEnterCB, never()).invoke() 84 85 // After the exit animator completes, 86 // the enter cb should run without other state changes 87 handler.onAnimationEnd(enterAnimator) 88 verify(exitAnimator, times(1)).start() 89 verify(enterAnimator, times(1)).start() 90 verify(postExitCB, times(1)).invoke() 91 verify(postEnterCB, times(1)).invoke() 92 } 93 94 @Test rebindSameData_executesFirstCallbacknull95 fun rebindSameData_executesFirstCallback() { 96 val postExitCB2 = mock(Callback::class.java) 97 98 handler.setNext("data-1", postExitCB, postEnterCB) 99 handler.setNext("data-1", postExitCB2, postEnterCB) 100 handler.onAnimationEnd(exitAnimator) 101 102 verify(postExitCB, times(1)).invoke() 103 verify(postExitCB2, never()).invoke() 104 verify(postEnterCB, never()).invoke() 105 } 106 107 @Test rebindDifferentData_executesSecondCallbacknull108 fun rebindDifferentData_executesSecondCallback() { 109 val postExitCB2 = mock(Callback::class.java) 110 111 handler.setNext("data-1", postExitCB, postEnterCB) 112 handler.setNext("data-2", postExitCB2, postEnterCB) 113 handler.onAnimationEnd(exitAnimator) 114 115 verify(postExitCB, never()).invoke() 116 verify(postExitCB2, times(1)).invoke() 117 verify(postEnterCB, never()).invoke() 118 } 119 120 @Test rebindBeforeEnterComplete_animationRestartsnull121 fun rebindBeforeEnterComplete_animationRestarts() { 122 val postExitCB2 = mock(Callback::class.java) 123 val postEnterCB2 = mock(Callback::class.java) 124 125 // We expect this first call to only start the exit animator 126 handler.setNext("data-1", postExitCB, postEnterCB) 127 verify(exitAnimator, times(1)).start() 128 verify(enterAnimator, never()).start() 129 verify(postExitCB, never()).invoke() 130 verify(postExitCB2, never()).invoke() 131 verify(postEnterCB, never()).invoke() 132 verify(postEnterCB2, never()).invoke() 133 134 // After the exit animator completes, 135 // the exit cb should run, and enter animation should start 136 whenever(exitAnimator.isRunning()).thenReturn(true) 137 whenever(enterAnimator.isRunning()).thenReturn(false) 138 handler.onAnimationEnd(exitAnimator) 139 verify(exitAnimator, times(1)).start() 140 verify(enterAnimator, times(1)).start() 141 verify(postExitCB, times(1)).invoke() 142 verify(postExitCB2, never()).invoke() 143 verify(postEnterCB, never()).invoke() 144 verify(postEnterCB2, never()).invoke() 145 146 // Setting new data before the enter animator completes should not trigger 147 // the exit animator an additional time (since it's already running) 148 whenever(exitAnimator.isRunning()).thenReturn(false) 149 whenever(enterAnimator.isRunning()).thenReturn(true) 150 handler.setNext("data-2", postExitCB2, postEnterCB2) 151 verify(exitAnimator, times(1)).start() 152 153 // Finishing the enterAnimator should cause the exitAnimator to fire again 154 // since the data change and additional time. No enterCB should be executed. 155 handler.onAnimationEnd(enterAnimator) 156 verify(exitAnimator, times(2)).start() 157 verify(enterAnimator, times(1)).start() 158 verify(postExitCB, times(1)).invoke() 159 verify(postExitCB2, never()).invoke() 160 verify(postEnterCB, never()).invoke() 161 verify(postEnterCB2, never()).invoke() 162 163 // Continuing the sequence, this triggers the enter animator an additional time 164 handler.onAnimationEnd(exitAnimator) 165 verify(exitAnimator, times(2)).start() 166 verify(enterAnimator, times(2)).start() 167 verify(postExitCB, times(1)).invoke() 168 verify(postExitCB2, times(1)).invoke() 169 verify(postEnterCB, never()).invoke() 170 verify(postEnterCB2, never()).invoke() 171 172 // And finally the enter animator completes, 173 // triggering the correct postEnterCallback to fire 174 handler.onAnimationEnd(enterAnimator) 175 verify(exitAnimator, times(2)).start() 176 verify(enterAnimator, times(2)).start() 177 verify(postExitCB, times(1)).invoke() 178 verify(postExitCB2, times(1)).invoke() 179 verify(postEnterCB, never()).invoke() 180 verify(postEnterCB2, times(1)).invoke() 181 } 182 183 @Test exitAnimationEndMultipleCalls_singleCallbackExecutionnull184 fun exitAnimationEndMultipleCalls_singleCallbackExecution() { 185 handler.setNext("data-1", postExitCB, postEnterCB) 186 handler.onAnimationEnd(exitAnimator) 187 handler.onAnimationEnd(exitAnimator) 188 handler.onAnimationEnd(exitAnimator) 189 190 verify(postExitCB, times(1)).invoke() 191 } 192 193 @Test enterAnimatorEndsWithoutCallback_noAnimatiorStartnull194 fun enterAnimatorEndsWithoutCallback_noAnimatiorStart() { 195 handler.onAnimationEnd(enterAnimator) 196 197 verify(exitAnimator, never()).start() 198 verify(enterAnimator, never()).start() 199 } 200 } 201