From c0a8f7ea99cb39ce072b21a0fb1df4e3ee6c8313 Mon Sep 17 00:00:00 2001 From: Brian Isganitis Date: Tue, 11 Mar 2025 13:30:37 -0400 Subject: [PATCH] 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 --- quickstep/res/values/dimens.xml | 3 + ...skbarRunningAppStateAnimationController.kt | 276 ++++++++++++++++++ .../taskbar/TaskbarViewController.java | 22 +- ...rRunningAppStateAnimationControllerTest.kt | 175 +++++++++++ .../launcher3/taskbar/TaskbarViewTestUtil.kt | 32 +- res/values/dimens.xml | 3 + src/com/android/launcher3/BubbleTextView.java | 129 ++------ .../util/MultiTranslateDelegate.java | 3 +- 8 files changed, 517 insertions(+), 126 deletions(-) create mode 100644 quickstep/src/com/android/launcher3/taskbar/TaskbarRunningAppStateAnimationController.kt create mode 100644 quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRunningAppStateAnimationControllerTest.kt diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index cb3c446dd9..9a68389354 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -372,6 +372,9 @@ 2dp 12dp 4dp + 6dp + 20dp + 4dp 12dp diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRunningAppStateAnimationController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRunningAppStateAnimationController.kt new file mode 100644 index 0000000000..4e690f192b --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRunningAppStateAnimationController.kt @@ -0,0 +1,276 @@ +/* + * 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.AnimatorSet +import android.animation.ArgbEvaluator +import android.animation.ObjectAnimator +import android.content.Context +import android.graphics.Color.TRANSPARENT +import android.util.FloatProperty +import android.util.IntProperty +import androidx.annotation.ColorInt +import androidx.annotation.Px +import androidx.annotation.UiThread +import androidx.core.animation.doOnEnd +import com.android.app.animation.Interpolators.EMPHASIZED +import com.android.app.animation.Interpolators.LINEAR +import com.android.internal.dynamicanimation.animation.FloatValueHolder +import com.android.internal.dynamicanimation.animation.SpringAnimation +import com.android.internal.dynamicanimation.animation.SpringForce +import com.android.internal.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY +import com.android.internal.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY +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.R +import com.android.launcher3.Utilities as LauncherUtilities +import com.android.launcher3.model.data.TaskItemInfo +import com.android.launcher3.taskbar.TaskbarViewController.TRANSITION_DEFAULT_DURATION +import com.android.launcher3.util.MultiPropertyFactory +import com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_APP_RUNNING_STATE_ANIM + +private const val SPRING_START = 0f +private const val SPRING_END = 100f + +private val SPRING_NO_BOUNCY = + SpringForce(SPRING_END).apply { + dampingRatio = DAMPING_RATIO_NO_BOUNCY + stiffness = 3800f + } +private val SPRING_MEDIUM_BOUNCY = + SpringForce(SPRING_END).apply { + dampingRatio = DAMPING_RATIO_MEDIUM_BOUNCY + stiffness = 400f + } + +private val TRANSLATE_Y_PROPERTY = + object : FloatProperty("runningStateTranslateY") { + private val BubbleTextView.runningStateTranslateYProp: MultiPropertyFactory<*>.MultiProperty + get() = translateDelegate.getTranslationY(INDEX_TASKBAR_APP_RUNNING_STATE_ANIM) + + override fun get(btv: BubbleTextView): Float = btv.runningStateTranslateYProp.value + + override fun setValue(btv: BubbleTextView, translateY: Float) { + btv.runningStateTranslateYProp.value = translateY + btv.invalidate() + } + } + +private val LINE_COLOR_PROPERTY = + object : IntProperty("lineIndicatorColor") { + override fun get(btv: BubbleTextView): Int = btv.lineIndicatorColor + + override fun setValue(btv: BubbleTextView, color: Int) { + btv.lineIndicatorColor = color + } + } + +private val LINE_WIDTH_PROPERTY = + object : FloatProperty("lineIndicatorWidth") { + override fun get(btv: BubbleTextView): Float = btv.lineIndicatorWidth + + override fun setValue(btv: BubbleTextView, width: Float) { + btv.lineIndicatorWidth = width + } + } + +/** Manages Taskbar [BubbleTextView] animations from changes in [RunningAppState]. */ +class TaskbarRunningAppStateAnimationController(context: Context) { + + /** Animating [BubbleTextView] instances, where invoking each value cancels the animation. */ + private val runningAnimations = mutableMapOf Unit>() + + private val argbEvaluator = ArgbEvaluator() + private val runningLineColor = + context.resources.getColor(R.color.taskbar_running_app_indicator_color, context.theme) + private val minimizedLineColor = + context.resources.getColor(R.color.taskbar_minimized_app_indicator_color, context.theme) + + private val runningLineWidth = + context.resources + .getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width) + .toFloat() + private val minimizedLineWidth = + context.resources + .getDimensionPixelSize(R.dimen.taskbar_minimized_app_indicator_width) + .toFloat() + + private val appTranslateYSpring = + context.resources.getDimensionPixelSize(R.dimen.taskbar_app_translate_y_spring).toFloat() + private val lineWidthSpring = + context.resources + .getDimensionPixelSize(R.dimen.taskbar_line_indicator_width_spring) + .toFloat() + + // Copies the keys to avoid concurrent modification due to value callbacks modifying the map. + fun onDestroy() = runningAnimations.keys.toList().forEach { cancelAnimation(it) } + + @UiThread + fun updateRunningState(btv: BubbleTextView, runningState: RunningAppState, animate: Boolean) { + val prevRunningState = btv.runningAppState ?: NOT_RUNNING + if (runningState == prevRunningState) return + + cancelAnimation(btv) + btv.runningAppState = runningState + if (!animate) return applyRunningState(btv) + + val isPinnedApp = btv.tag is TaskItemInfo + if ( + (prevRunningState == RUNNING && runningState == MINIMIZED) || + (prevRunningState == MINIMIZED && runningState == RUNNING) || + (isPinnedApp && runningState == RUNNING) + ) { + return startAppBounceAnimation(btv) + } + + // Otherwise just animate line width and color. + AnimatorSet().run { + if (runningState == RUNNING) { + // New unpinned app - delay animation until icon is mostly scaled in. + startDelay = UNPINNED_APP_LINE_ANIM_DELAY + } + + duration = LINE_ANIM_DURATION + interpolator = EMPHASIZED + + playTogether( + ObjectAnimator.ofFloat(btv, LINE_WIDTH_PROPERTY, runningState.lineWidth), + ObjectAnimator.ofArgb(btv, LINE_COLOR_PROPERTY, runningState.lineColor), + ) + + doOnEnd { + runningAnimations.remove(btv) + applyRunningState(btv) + } + + runningAnimations[btv] = this::cancel + start() + } + } + + fun isAnimationRunning(btv: BubbleTextView): Boolean = runningAnimations.containsKey(btv) + + private fun cancelAnimation(btv: BubbleTextView) = runningAnimations[btv]?.invoke() + + private fun startAppBounceAnimation(btv: BubbleTextView) { + val isMinimized = btv.runningAppState == MINIMIZED + val translateYSpring = if (isMinimized) appTranslateYSpring else -appTranslateYSpring + val prevLineWidth = btv.lineIndicatorWidth + val prevLineColor = btv.lineIndicatorColor + + val translateYProp = + btv.translateDelegate.getTranslationY(INDEX_TASKBAR_APP_RUNNING_STATE_ANIM) + fun updateTranslateY(value: Float) { + translateYProp.value = value + btv.invalidate() + } + + SpringAnimation(FloatValueHolder()).run { + spring = SPRING_NO_BOUNCY + addUpdateListener { _, v, _ -> + updateTranslateY(mapValue(v, 0f, translateYSpring)) + if (isMinimized) { + btv.lineIndicatorWidth = mapValue(v, prevLineWidth, lineWidthSpring) + } + } + + addEndListener { _, canceled, _, _ -> + runningAnimations.remove(btv) + if (canceled) return@addEndListener applyRunningState(btv) + + val startLineWidth = if (isMinimized) lineWidthSpring else prevLineWidth + val endLineWidth = btv.runningAppState.lineWidth + val endLineColor = btv.runningAppState.lineColor + + val springs = + listOf( + SpringAnimation(FloatValueHolder()).apply { + spring = SPRING_MEDIUM_BOUNCY + addUpdateListener { _, v, _ -> + updateTranslateY(mapValue(v, translateYSpring, 0f)) + btv.lineIndicatorWidth = mapValue(v, startLineWidth, endLineWidth) + } + }, + SpringAnimation(FloatValueHolder()).apply { + spring = SPRING_NO_BOUNCY + addUpdateListener { _, v, _ -> + btv.lineIndicatorColor = + argbEvaluator.evaluate( + v / SPRING_END, + prevLineColor, + endLineColor, + ) as Int + } + }, + ) + + runningAnimations[btv] = { for (s in springs) s.cancel() } + var runningSprings = springs.size + for (s in springs) { + s.addEndListener { _, canceled2, _, _ -> + if (--runningSprings == 0) { + runningAnimations.remove(btv) + if (canceled2) applyRunningState(btv) + } + } + s.start() + } + } + + runningAnimations[btv] = this::cancel + start() + } + } + + private fun applyRunningState(btv: BubbleTextView) { + btv.lineIndicatorWidth = btv.runningAppState.lineWidth + btv.lineIndicatorColor = btv.runningAppState.lineColor + TRANSLATE_Y_PROPERTY[btv] = 0f + } + + private fun mapValue(value: Float, min: Float, max: Float): Float { + return LauncherUtilities.mapToRange(value, SPRING_START, SPRING_END, min, max, LINEAR) + } + + @get:ColorInt + val RunningAppState.lineColor: Int + get() { + return when (this) { + NOT_RUNNING -> TRANSPARENT + RUNNING -> runningLineColor + MINIMIZED -> minimizedLineColor + } + } + + @get:Px + val RunningAppState.lineWidth: Float + get() { + return when (this) { + NOT_RUNNING -> 0f + RUNNING -> runningLineWidth + MINIMIZED -> minimizedLineWidth + } + } + + companion object { + const val LINE_ANIM_DURATION = 100L + const val UNPINNED_APP_LINE_ANIM_DELAY = TRANSITION_DEFAULT_DURATION - LINE_ANIM_DURATION + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index c5b97e7bbc..eca2e11735 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -25,7 +25,6 @@ import static android.window.DesktopModeFlags.ENABLE_TASKBAR_RECENTS_LAYOUT_TRAN import static com.android.app.animation.Interpolators.EMPHASIZED; import static com.android.app.animation.Interpolators.FINAL_FRAME; import static com.android.app.animation.Interpolators.LINEAR; -import static com.android.launcher3.BubbleTextView.LINE_INDICATOR_ANIM_DURATION; import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.Flags.taskbarOverflow; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; @@ -136,11 +135,9 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar private static final float ERROR_POSITION_IN_HOTSEAT_NOT_FOUND = -100; private static final int TRANSITION_DELAY = 50; - private static final int TRANSITION_DEFAULT_DURATION = 500; + static final int TRANSITION_DEFAULT_DURATION = 500; private static final int TRANSITION_FADE_IN_DURATION = 167; private static final int TRANSITION_FADE_OUT_DURATION = 83; - private static final int APPEARING_LINE_INDICATOR_ANIM_DELAY = - TRANSITION_DEFAULT_DURATION - LINE_INDICATOR_ANIM_DURATION; private final TaskbarActivityContext mActivity; private @Nullable TaskbarDragLayerController mDragLayerController; @@ -224,6 +221,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar private final float mTaskbarLeftRightMargin; + private final TaskbarRunningAppStateAnimationController mRunningStateController; + public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) { mActivity = activity; mTransientTaskbarDp = mActivity.getTransientTaskbarDeviceProfile(); @@ -242,6 +241,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar mIsRtl = Utilities.isRtl(mTaskbarView.getResources()); mTaskbarLeftRightMargin = mActivity.getResources().getDimensionPixelSize( R.dimen.transient_taskbar_padding); + mRunningStateController = new TaskbarRunningAppStateAnimationController(mActivity); } /** @@ -395,6 +395,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar } LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks); mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener); + mRunningStateController.onDestroy(); } /** @@ -773,7 +774,10 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar } private void updateRunningState(BubbleTextView btv) { - btv.updateRunningState(getRunningAppState(btv), mTaskbarView.getLayoutTransition() != null); + mRunningStateController.updateRunningState( + btv, + getRunningAppState(btv), + /* animate = */ mTaskbarView.getLayoutTransition() != null); } private BubbleTextView.RunningAppState getRunningAppState(BubbleTextView btv) { @@ -1233,10 +1237,6 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar view.setAlpha(0f); view.setScaleX(0f); view.setScaleY(0f); - if (view instanceof BubbleTextView btv) { - // Defer so that app is mostly scaled in before showing indicator. - btv.setLineIndicatorAnimStartDelay(APPEARING_LINE_INDICATOR_ANIM_DELAY); - } } else if (type == DISAPPEARING && view instanceof BubbleTextView btv) { // Running state updates happen after removing this view, so update it here. updateRunningState(btv); @@ -1246,9 +1246,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar @Override public void endTransition( LayoutTransition transition, ViewGroup container, View view, int type) { - if (type == APPEARING && view instanceof BubbleTextView btv) { - btv.setLineIndicatorAnimStartDelay(0); - } + // Do nothing. } }); diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRunningAppStateAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRunningAppStateAnimationControllerTest.kt new file mode 100644 index 0000000000..bdc8901ec7 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRunningAppStateAnimationControllerTest.kt @@ -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) + } + } +} diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt index 92abbbaa0a..4c7c81caae 100644 --- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt @@ -83,21 +83,23 @@ object TaskbarViewTestUtil { /** Creates a list of fake recent tasks. */ fun createRecents(size: Int): List { - 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(), + ) + } + ) } } diff --git a/res/values/dimens.xml b/res/values/dimens.xml index a15c130076..39dc4fb1ac 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -445,6 +445,9 @@ 0dp 0dp 0dp + 0dp + 0dp + 0dp 0dp diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 30e3a2b577..1225ae6b72 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -20,9 +20,7 @@ import static android.graphics.fonts.FontStyle.FONT_WEIGHT_BOLD; import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL; import static android.text.Layout.Alignment.ALIGN_NORMAL; -import static com.android.app.animation.Interpolators.EMPHASIZED; import static com.android.launcher3.BubbleTextView.RunningAppState.RUNNING; -import static com.android.launcher3.BubbleTextView.RunningAppState.NOT_RUNNING; import static com.android.launcher3.BubbleTextView.RunningAppState.MINIMIZED; import static com.android.launcher3.Flags.enableContrastTiles; import static com.android.launcher3.Flags.enableCursorHoverStates; @@ -36,10 +34,10 @@ import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTO import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK; +import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_APP_RUNNING_STATE_ANIM; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.ColorStateList; @@ -134,9 +132,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, StringMatcherUtility.StringMatcher.getInstance(); private static final int BOLD_TEXT_ADJUSTMENT = FONT_WEIGHT_BOLD - FONT_WEIGHT_NORMAL; - public static final int LINE_INDICATOR_ANIM_DURATION = 150; - private static final float MINIMIZED_APP_INDICATOR_SCALE = 0.5f; - private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed}; private float mScaleForReorderBounce = 1f; @@ -172,36 +167,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } }; - private static final Property LINE_INDICATOR_COLOR_PROPERTY = - new Property<>(Integer.class, "lineIndicatorColor") { - - @Override - public Integer get(BubbleTextView bubbleTextView) { - return bubbleTextView.mLineIndicatorColor; - } - - @Override - public void set(BubbleTextView bubbleTextView, Integer color) { - bubbleTextView.mLineIndicatorColor = color; - bubbleTextView.invalidate(); - } - }; - - private static final Property LINE_INDICATOR_SCALE_PROPERTY = - new Property<>(Float.TYPE, "lineIndicatorScale") { - - @Override - public Float get(BubbleTextView bubbleTextView) { - return bubbleTextView.mLineIndicatorScale; - } - - @Override - public void set(BubbleTextView bubbleTextView, Float scale) { - bubbleTextView.mLineIndicatorScale = scale; - bubbleTextView.invalidate(); - } - }; - private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this); protected final ActivityContext mActivity; private FastBitmapDrawable mIcon; @@ -238,20 +203,16 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private boolean mForceHideDot; // These fields, related to showing running apps, are only used for Taskbar. - private final int mRunningAppIndicatorWidth; private final int mRunningAppIndicatorHeight; private final int mRunningAppIndicatorTopMargin; private final Paint mRunningAppIndicatorPaint; private final Rect mRunningAppIconBounds = new Rect(); private RunningAppState mRunningAppState; - private final int mRunningAppIndicatorColor; - private final int mMinimizedAppIndicatorColor; + @ViewDebug.ExportedProperty(category = "launcher") private int mLineIndicatorColor; @ViewDebug.ExportedProperty(category = "launcher") - private float mLineIndicatorScale; - private int mLineIndicatorAnimStartDelay; - private Animator mLineIndicatorAnim; + private float mLineIndicatorWidth; private final String mMinimizedStateDescription; private final String mRunningStateDescription; @@ -334,19 +295,12 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, defaultIconSize); a.recycle(); - mRunningAppIndicatorWidth = - getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width); mRunningAppIndicatorHeight = getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_height); mRunningAppIndicatorTopMargin = getResources().getDimensionPixelSize( R.dimen.taskbar_running_app_indicator_top_margin); - mRunningAppIndicatorPaint = new Paint(); - mRunningAppIndicatorColor = getResources().getColor( - R.color.taskbar_running_app_indicator_color, context.getTheme()); - mMinimizedAppIndicatorColor = getResources().getColor( - R.color.taskbar_minimized_app_indicator_color, context.getTheme()); mLongPressHelper = new CheckLongPressHelper(this); @@ -385,9 +339,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, setBackground(null); mLineIndicatorColor = Color.TRANSPARENT; - mLineIndicatorScale = 0; - mLineIndicatorAnimStartDelay = 0; - cancelLineIndicatorAnim(); + mLineIndicatorWidth = 0; setTag(null); if (mIconLoadRequest != null) { @@ -479,52 +431,30 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, setContentDescription(label); } - /** Updates whether the app this view represents is currently running. */ - @UiThread - public void updateRunningState(RunningAppState runningAppState, boolean animate) { - if (runningAppState.equals(mRunningAppState)) { - return; - } + public void setRunningAppState(RunningAppState runningAppState) { mRunningAppState = runningAppState; - cancelLineIndicatorAnim(); - - int color = switch (mRunningAppState) { - case NOT_RUNNING -> Color.TRANSPARENT; - case RUNNING -> mRunningAppIndicatorColor; - case MINIMIZED -> mMinimizedAppIndicatorColor; - }; - float scale = switch (mRunningAppState) { - case NOT_RUNNING -> 0; - case RUNNING -> 1; - case MINIMIZED -> MINIMIZED_APP_INDICATOR_SCALE; - }; - - if (!animate) { - mLineIndicatorColor = color; - mLineIndicatorScale = scale; - invalidate(); - return; - } - - AnimatorSet lineIndicatorAnim = new AnimatorSet(); - mLineIndicatorAnim = lineIndicatorAnim; - Animator colorAnimator = ObjectAnimator.ofArgb(this, LINE_INDICATOR_COLOR_PROPERTY, color); - Animator scaleAnimator = ObjectAnimator.ofFloat(this, LINE_INDICATOR_SCALE_PROPERTY, scale); - lineIndicatorAnim.playTogether(colorAnimator, scaleAnimator); - - lineIndicatorAnim.setInterpolator(EMPHASIZED); - lineIndicatorAnim.setStartDelay(mLineIndicatorAnimStartDelay); - lineIndicatorAnim.setDuration(LINE_INDICATOR_ANIM_DURATION).start(); } - public void setLineIndicatorAnimStartDelay(int lineIndicatorAnimStartDelay) { - mLineIndicatorAnimStartDelay = lineIndicatorAnimStartDelay; + public RunningAppState getRunningAppState() { + return mRunningAppState; } - private void cancelLineIndicatorAnim() { - if (mLineIndicatorAnim != null) { - mLineIndicatorAnim.cancel(); - } + public int getLineIndicatorColor() { + return mLineIndicatorColor; + } + + public void setLineIndicatorColor(int lineIndicatorColor) { + mLineIndicatorColor = lineIndicatorColor; + invalidate(); + } + + public float getLineIndicatorWidth() { + return mLineIndicatorWidth; + } + + public void setLineIndicatorWidth(float lineIndicatorWidth) { + mLineIndicatorWidth = lineIndicatorWidth; + invalidate(); } /** @@ -879,22 +809,25 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, /** Draws a line under the app icon if this is representing a running app in Desktop Mode. */ protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) { if (mDisplay != DISPLAY_TASKBAR - || mLineIndicatorScale == 0 + || Float.compare(mLineIndicatorWidth, 0) == 0 || mLineIndicatorColor == Color.TRANSPARENT) { return; } getIconBounds(mRunningAppIconBounds); Utilities.scaleRectAboutCenter(mRunningAppIconBounds, ICON_VISIBLE_AREA_FACTOR); - final int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin; - final float indicatorWidth = mRunningAppIndicatorWidth * mLineIndicatorScale; + float taskbarAppRunningStateAnimOffset = + mTranslateDelegate.getTranslationY(INDEX_TASKBAR_APP_RUNNING_STATE_ANIM).getValue(); + final float indicatorTop = mRunningAppIconBounds.bottom + + mRunningAppIndicatorTopMargin + - taskbarAppRunningStateAnimOffset; final float cornerRadius = mRunningAppIndicatorHeight / 2f; mRunningAppIndicatorPaint.setColor(mLineIndicatorColor); canvas.drawRoundRect( - mRunningAppIconBounds.centerX() - indicatorWidth / 2f, + mRunningAppIconBounds.centerX() - mLineIndicatorWidth / 2f, indicatorTop, - mRunningAppIconBounds.centerX() + indicatorWidth / 2f, + mRunningAppIconBounds.centerX() + mLineIndicatorWidth / 2f, indicatorTop + mRunningAppIndicatorHeight, cornerRadius, cornerRadius, diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java index ce006c4184..ecec39c2ef 100644 --- a/src/com/android/launcher3/util/MultiTranslateDelegate.java +++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java @@ -39,6 +39,7 @@ public class MultiTranslateDelegate { public static final int INDEX_TASKBAR_PINNING_ANIM = 5; public static final int INDEX_NAV_BAR_ANIM = 6; public static final int INDEX_BUBBLE_BAR_ANIM = 7; + public static final int INDEX_TASKBAR_APP_RUNNING_STATE_ANIM = 8; // Affect all items inside of a MultipageCellLayout public static final int INDEX_CELLAYOUT_MULTIPAGE_SPACING = 3; @@ -49,7 +50,7 @@ public class MultiTranslateDelegate { // Specific for hotseat items when adjusting for bubbles public static final int INDEX_BUBBLE_ADJUSTMENT_ANIM = 3; - public static final int COUNT = 8; + public static final int COUNT = 9; private final MultiPropertyFactory mTranslationX; private final MultiPropertyFactory mTranslationY;