Implement spring animations for running state changes.

Flag: com.android.window.flags.enable_taskbar_recents_layout_transition
Fix: 402683858
Test: go/testedequals
Change-Id: Icc4a3a61b6c0997564a4dcdd8eb7768459d5f2ef
This commit is contained in:
Brian Isganitis
2025-03-11 13:30:37 -04:00
parent ddc72547d4
commit c0a8f7ea99
8 changed files with 517 additions and 126 deletions

View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2025 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.taskbar
import android.animation.AnimatorTestRule
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.BubbleTextView
import com.android.launcher3.BubbleTextView.RunningAppState
import com.android.launcher3.BubbleTextView.RunningAppState.MINIMIZED
import com.android.launcher3.BubbleTextView.RunningAppState.NOT_RUNNING
import com.android.launcher3.BubbleTextView.RunningAppState.RUNNING
import com.android.launcher3.model.data.TaskItemInfo
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
import com.android.launcher3.taskbar.TaskbarRunningAppStateAnimationController.Companion.LINE_ANIM_DURATION
import com.android.launcher3.taskbar.TaskbarRunningAppStateAnimationController.Companion.UNPINNED_APP_LINE_ANIM_DELAY
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_APP_RUNNING_STATE_ANIM
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
private const val FRAME_TIME_MS = 16L // Simulates 60 Hz.
private val PINNED_APP = TaskItemInfo(0, TaskbarViewTestUtil.createHotseatWorkspaceItem(0))
private val UNPINNED_APP = TaskbarViewTestUtil.createRecentTask(1)
@RunWith(LauncherMultivalentJUnit::class)
class TaskbarRunningAppStateAnimationControllerTest {
@get:Rule val animatorTestRule = AnimatorTestRule(this)
private val context = ActivityContextWrapper(getInstrumentation().targetContext)
private val btv = BubbleTextView(context)
private val controller = TaskbarRunningAppStateAnimationController(context)
@Test
fun updateRunningState_minimizeApp_verifySpringEndState() {
startStateChange(start = RUNNING, end = MINIMIZED)
verifySpringAnimationEnd(MINIMIZED)
}
@Test
fun updateRunningState_minimizeApp_verifyCancelEndState() {
startStateChange(start = RUNNING, end = MINIMIZED)
runOnMainSync { controller.onDestroy() }
verifyStateSettled(state = MINIMIZED)
}
@Test
fun updateRunningState_restoreApp_verifySpringEndState() {
startStateChange(start = MINIMIZED, end = RUNNING)
verifySpringAnimationEnd(RUNNING)
}
@Test
fun updateRunningState_openPinnedApp_verifySpringEndState() {
btv.tag = PINNED_APP
startStateChange(start = NOT_RUNNING, end = RUNNING)
verifySpringAnimationEnd(RUNNING)
}
@Test
fun updateRunningState_openUnpinnedApp_verifyStartDelay() {
btv.tag = UNPINNED_APP
startStateChange(start = NOT_RUNNING, end = RUNNING)
runOnMainSync { animatorTestRule.advanceTimeBy(UNPINNED_APP_LINE_ANIM_DELAY) }
verifyLineIndicator(state = NOT_RUNNING)
}
@Test
fun updateRunningState_openUnpinnedApp_verifyEndState() {
btv.tag = UNPINNED_APP
startStateChange(start = NOT_RUNNING, end = RUNNING)
runOnMainSync {
animatorTestRule.advanceTimeBy(UNPINNED_APP_LINE_ANIM_DELAY + LINE_ANIM_DURATION)
}
verifyStateSettled(state = RUNNING)
}
@Test
fun updateRunningState_openUnpinnedApp_verifyCancelEndState() {
btv.tag = UNPINNED_APP
startStateChange(start = NOT_RUNNING, end = RUNNING)
runOnMainSync { controller.onDestroy() }
verifyStateSettled(state = RUNNING)
}
@Test
fun updateRunningState_closeApp_verifyEndState() {
startStateChange(start = RUNNING, end = NOT_RUNNING)
runOnMainSync { animatorTestRule.advanceTimeBy(LINE_ANIM_DURATION) }
verifyStateSettled(state = NOT_RUNNING)
}
@Test
fun updateRunningState_repeatUpdateDuringAnimation_animationNotCanceled() {
startStateChange(start = MINIMIZED, end = RUNNING)
runOnMainSync {
animatorTestRule.advanceTimeBy(FRAME_TIME_MS)
controller.updateRunningState(btv, RUNNING, animate = false)
}
assertThat(controller.isAnimationRunning(btv)).isTrue()
}
@Test
fun updateRunningState_minimizedDuringOpen_verifyMinimizedEndState() {
startStateChange(start = NOT_RUNNING, end = RUNNING)
runOnMainSync { controller.updateRunningState(btv, MINIMIZED, animate = true) }
verifySpringAnimationEnd(MINIMIZED)
}
@Test
fun onDestroy_multipleAnimations_cancelsAll() {
startStateChange(start = RUNNING, end = MINIMIZED)
val btv2 = BubbleTextView(context)
startStateChange(btv = btv2, start = RUNNING, end = MINIMIZED)
runOnMainSync { controller.onDestroy() }
verifyStateSettled(state = MINIMIZED)
verifyStateSettled(btv = btv2, state = MINIMIZED)
}
private fun startStateChange(
btv: BubbleTextView = this.btv,
start: RunningAppState,
end: RunningAppState,
) {
runOnMainSync {
controller.updateRunningState(btv, start, animate = false)
controller.updateRunningState(btv, end, animate = true)
}
verifyLineIndicator(btv, start)
assertThat(controller.isAnimationRunning(btv)).isTrue()
}
/** Verifies [btv] spring animation ends at [state]. */
private fun verifySpringAnimationEnd(state: RunningAppState) {
while (controller.isAnimationRunning(btv)) {
runOnMainSync { animatorTestRule.advanceTimeBy(FRAME_TIME_MS) }
}
verifyStateSettled(state = state)
}
private fun verifyStateSettled(btv: BubbleTextView = this.btv, state: RunningAppState) {
assertThat(controller.isAnimationRunning(btv)).isFalse()
verifyLineIndicator(btv, state)
val translateYProp =
btv.translateDelegate.getTranslationY(INDEX_TASKBAR_APP_RUNNING_STATE_ANIM)
assertThat(translateYProp.value).isZero()
}
private fun verifyLineIndicator(btv: BubbleTextView = this.btv, state: RunningAppState) {
controller.run {
assertThat(btv.lineIndicatorWidth).isEqualTo(state.lineWidth)
assertThat(btv.lineIndicatorColor).isEqualTo(state.lineColor)
}
}
}

View File

@@ -83,21 +83,23 @@ object TaskbarViewTestUtil {
/** Creates a list of fake recent tasks. */
fun createRecents(size: Int): List<GroupTask> {
return List(size) {
SingleTask(
Task().apply {
key =
TaskKey(
it,
5,
TEST_INTENT,
TEST_COMPONENT,
Process.myUserHandle().identifier,
System.currentTimeMillis(),
)
}
)
}
return List(size) { createRecentTask(it) }
}
fun createRecentTask(id: Int = 0): GroupTask {
return SingleTask(
Task().apply {
key =
TaskKey(
id,
5,
TEST_INTENT,
TEST_COMPONENT,
Process.myUserHandle().identifier,
System.currentTimeMillis(),
)
}
)
}
}