diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 477f90cedf..5374280c71 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -17,6 +17,8 @@ package com.android.launcher3.taskbar; import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY; +import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_APP_DURATION; +import static com.android.launcher3.QuickstepTransitionManager.getTaskbarToHomeDuration; import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION; import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE; import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES; @@ -32,7 +34,6 @@ import androidx.annotation.Nullable; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Flags; import com.android.launcher3.LauncherState; -import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.logging.InstanceId; @@ -204,11 +205,17 @@ public class LauncherTaskbarUIController extends TaskbarUIController { isVisible, fromInitOrDestroy, /* startAnimation= */ true, - DisplayController.isTransientTaskbar(mLauncher) - ? TRANSIENT_TASKBAR_TRANSITION_DURATION - : (!isVisible - ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION - : QuickstepTransitionManager.getTaskbarToHomeDuration())); + getTaskbarAnimationDuration(isVisible)); + } + + private int getTaskbarAnimationDuration(boolean isVisible) { + if (isVisible && !mLauncher.getPredictiveBackToHomeInProgress()) { + return getTaskbarToHomeDuration(); + } else { + return DisplayController.isTransientTaskbar(mLauncher) + ? TRANSIENT_TASKBAR_TRANSITION_DURATION + : TASKBAR_TO_APP_DURATION; + } } @Nullable diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt index 685c109eec..858f682c43 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt @@ -149,7 +149,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas } if ( taskbarStashController.isInApp || - taskbarStashController.isInOverview || + controllers.uiController.isInOverviewUi || DisplayController.showLockedTaskbarOnHome(context) ) { // only add the taskbar touch region if not on home diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java index 39ddb407fa..2be7162e84 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java @@ -16,6 +16,7 @@ package com.android.launcher3.taskbar; import static com.android.app.animation.Interpolators.EMPHASIZED; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_ALIGNMENT; import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_STASH; import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; @@ -39,6 +40,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.os.SystemClock; import android.util.Log; +import android.view.animation.Interpolator; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -58,6 +60,7 @@ import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.MultiPropertyFactory.MultiProperty; import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationController; +import com.android.quickstep.util.ScalingWorkspaceRevealAnim; import com.android.quickstep.util.SystemUiFlagUtils; import com.android.quickstep.views.RecentsView; import com.android.systemui.animation.ViewRootSync; @@ -666,7 +669,9 @@ public class TaskbarLauncherStateController { animatorSet.play(iconAlignAnim); } - animatorSet.setInterpolator(EMPHASIZED); + Interpolator interpolator = enableScalingRevealHomeAnimation() + ? ScalingWorkspaceRevealAnim.SCALE_INTERPOLATOR : EMPHASIZED; + animatorSet.setInterpolator(interpolator); if (start) { animatorSet.start(); diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index e80e838e90..6371646422 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -1300,6 +1300,10 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer, mTISBindHelper.setPredictiveBackToHomeInProgress(isInProgress); } + public boolean getPredictiveBackToHomeInProgress() { + return mIsPredictiveBackToHomeInProgress; + } + @Override public boolean areDesktopTasksVisible() { DesktopVisibilityController desktopVisibilityController = getDesktopVisibilityController(); diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index a6d651c951..10b6081393 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -238,6 +238,8 @@ public abstract class AbsSwipeUpHandler< getNextStateFlag("STATE_SCALED_CONTROLLER_HOME"); private static final int STATE_SCALED_CONTROLLER_RECENTS = getNextStateFlag("STATE_SCALED_CONTROLLER_RECENTS"); + private static final int STATE_PARALLEL_ANIM_FINISHED = + getNextStateFlag("STATE_PARALLEL_ANIM_FINISHED"); protected static final int STATE_HANDLER_INVALIDATED = getNextStateFlag("STATE_HANDLER_INVALIDATED"); @@ -450,7 +452,8 @@ public abstract class AbsSwipeUpHandler< mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED | STATE_SCALED_CONTROLLER_HOME, this::finishCurrentTransitionToHome); - mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED, + mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED + | STATE_PARALLEL_ANIM_FINISHED, this::reset); mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED @@ -1548,9 +1551,12 @@ public abstract class AbsSwipeUpHandler< @Override public void onAnimationEnd(Animator animation) { mParallelRunningAnim = null; + mStateCallback.setStateOnUiThread(STATE_PARALLEL_ANIM_FINISHED); } }); mParallelRunningAnim.start(); + } else { + mStateCallback.setStateOnUiThread(STATE_PARALLEL_ANIM_FINISHED); } } diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt index f547a7fba1..df08ac8882 100644 --- a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt +++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt @@ -60,7 +60,8 @@ class ScalingWorkspaceRevealAnim( * Custom interpolator for both the home and wallpaper scaling. Necessary because EMPHASIZED * is too aggressive, but EMPHASIZED_DECELERATE is too soft. */ - private val SCALE_INTERPOLATOR = + @JvmField + val SCALE_INTERPOLATOR = PathInterpolator( Path().apply { moveTo(0f, 0f) diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java index 2a0aa4ccb1..d2dd639511 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java @@ -16,15 +16,23 @@ package com.android.quickstep; +import static com.android.quickstep.AbsSwipeUpHandler.STATE_HANDLER_INVALIDATED; + +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.animation.ValueAnimator; import android.app.ActivityManager; import android.content.Context; import android.content.Intent; @@ -54,6 +62,7 @@ import com.android.systemui.shared.system.InputConsumerController; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -207,7 +216,7 @@ public abstract class AbsSwipeUpHandlerTestCase< runOnMainSync(() -> { absSwipeUpHandler.startNewTask(unused -> {}); - verify(mRecentsAnimationController).finish(anyBoolean(), any()); + verifyRecentsAnimationFinishedAndCallCallback(); }); } @@ -217,10 +226,57 @@ public abstract class AbsSwipeUpHandlerTestCase< runOnMainSync(() -> { verify(mRecentsAnimationController).detachNavigationBarFromApp(true); - verify(mRecentsAnimationController).finish(anyBoolean(), any(), anyBoolean()); + verifyRecentsAnimationFinishedAndCallCallback(); }); } + @Test + public void testHomeGesture_invalidatesHandlerAfterParallelAnim() { + ValueAnimator parallelAnim = new ValueAnimator(); + parallelAnim.setRepeatCount(ValueAnimator.INFINITE); + when(mActivityInterface.getParallelAnimationToLauncher(any(), anyLong(), any())) + .thenReturn(parallelAnim); + SWIPE_HANDLER handler = createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME); + runOnMainSync(() -> { + parallelAnim.start(); + verifyRecentsAnimationFinishedAndCallCallback(); + assertFalse(handler.mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)); + parallelAnim.end(); + assertTrue(handler.mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)); + }); + } + + @Test + public void testHomeGesture_invalidatesHandlerIfNoParallelAnim() { + when(mActivityInterface.getParallelAnimationToLauncher(any(), anyLong(), any())) + .thenReturn(null); + SWIPE_HANDLER handler = createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME); + runOnMainSync(() -> { + verifyRecentsAnimationFinishedAndCallCallback(); + assertTrue(handler.mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)); + }); + } + + /** + * Verifies that RecentsAnimationController#finish() is called, and captures and runs any + * callback that was passed to it. This ensures that STATE_CURRENT_TASK_FINISHED is correctly + * set for example. + */ + private void verifyRecentsAnimationFinishedAndCallCallback() { + ArgumentCaptor finishCallback = ArgumentCaptor.forClass(Runnable.class); + // Check if the 2 parameter method is called. + verify(mRecentsAnimationController, atLeast(0)).finish( + anyBoolean(), finishCallback.capture()); + if (finishCallback.getAllValues().isEmpty()) { + // Check if the 3 parameter method is called. + verify(mRecentsAnimationController).finish( + anyBoolean(), finishCallback.capture(), anyBoolean()); + } + if (finishCallback.getValue() != null) { + finishCallback.getValue().run(); + } + } + private SWIPE_HANDLER createSwipeUpHandlerForGesture(GestureState.GestureEndTarget endTarget) { boolean isQuickSwitch = endTarget == GestureState.GestureEndTarget.NEW_TASK; diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 8121e53542..5224ee39d1 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -1130,6 +1130,9 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, if (itemInfo.isDisabled()) { setContentDescription(getContext().getString(R.string.disabled_app_label, itemInfo.contentDescription)); + } else if (itemInfo instanceof WorkspaceItemInfo wai && wai.isArchived()) { + setContentDescription( + getContext().getString(R.string.app_archived_title, itemInfo.title)); } else if (hasDot()) { int count = mDotInfo.getNotificationCount(); setContentDescription( @@ -1142,8 +1145,16 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } private void setDownloadStateContentDescription(ItemInfoWithIcon info, int progressLevel) { - if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_ARCHIVED) != 0 && progressLevel == 0) { - setContentDescription(getContext().getString(R.string.app_archived_title, info.title)); + if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_ARCHIVED) != 0 + && progressLevel == 0) { + if (mIcon instanceof PreloadIconDrawable) { + // Tell user that download is pending and not to tap to download again. + setContentDescription(getContext().getString( + R.string.app_waiting_download_title, info.title)); + } else { + setContentDescription(getContext().getString( + R.string.app_archived_title, info.title)); + } } else if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) { String percentageString = NumberFormat.getPercentInstance()