diff options
author | Tracy Zhou <tracyzhou@google.com> | 2021-11-08 00:48:53 -0800 |
---|---|---|
committer | Tracy Zhou <tracyzhou@google.com> | 2021-11-09 23:46:03 -0800 |
commit | e89a83b65d54d89da1a323c3b69fa2ee12677fbf (patch) | |
tree | 9b7edfea514767343d7b27dd4c739bf2d65e9569 /quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java | |
parent | f37d7a21f2b84cdcd995b21b186f08e9cab3210e (diff) |
Track LauncherState, RecentsAnimation, resumed state for task bar in one place
TODO:
- Consider delaying animating task bar to stashed towards all apps state until user releasing their finger (tho in this change heuristic is applied for stashing and unstashing respectively)
- Further consolidate some animation logic
Bug: 204220602
Test: manual
Change-Id: I58b4d035fcf65a9f5c68e69c129eae95b89b1c4a
Diffstat (limited to 'quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java')
-rw-r--r-- | quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java new file mode 100644 index 0000000000..2693bc343d --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2021 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 static com.android.launcher3.LauncherState.HOTSEAT_ICONS; +import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP; +import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE; +import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION; +import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; + +import androidx.annotation.NonNull; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.statemanager.StateManager; +import com.android.launcher3.util.MultiValueAlpha; +import com.android.quickstep.AnimatedFloat; +import com.android.quickstep.RecentsAnimationCallbacks; +import com.android.quickstep.RecentsAnimationController; +import com.android.quickstep.views.RecentsView; +import com.android.systemui.shared.recents.model.ThumbnailData; + +import java.util.HashMap; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate + * the task bar accordingly. + */ + public class TaskbarLauncherStateController { + + public static final int FLAG_RESUMED = 1 << 0; + public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1; + public static final int FLAG_TRANSITION_STATE_START_STASHED = 1 << 2; + public static final int FLAG_TRANSITION_STATE_COMMITTED_STASHED = 1 << 3; + + private final AnimatedFloat mIconAlignmentForResumedState = + new AnimatedFloat(this::onIconAlignmentRatioChanged); + private final AnimatedFloat mIconAlignmentForGestureState = + new AnimatedFloat(this::onIconAlignmentRatioChanged); + private final AnimatedFloat mIconAlignmentForLauncherState = + new AnimatedFloat(this::onIconAlignmentRatioChangedForStateTransition); + + private TaskbarControllers mControllers; + private AnimatedFloat mTaskbarBackgroundAlpha; + private MultiValueAlpha.AlphaProperty mIconAlphaForHome; + private BaseQuickstepLauncher mLauncher; + + private int mPrevState; + private int mState; + + private LauncherState mTargetStateOverride = null; + private LauncherState mTargetStateOverrideForStateTransition = null; + + private boolean mIsAnimatingToLauncherViaGesture; + private boolean mIsAnimatingToLauncherViaResume; + + private final StateManager.StateListener<LauncherState> mStateListener = + new StateManager.StateListener<LauncherState>() { + + @Override + public void onStateTransitionStart(LauncherState toState) { + mTargetStateOverrideForStateTransition = toState; + updateStateForFlag(FLAG_TRANSITION_STATE_START_STASHED, + toState.isTaskbarStashed()); + applyState(); + } + + @Override + public void onStateTransitionComplete(LauncherState finalState) { + updateStateForFlag(FLAG_TRANSITION_STATE_COMMITTED_STASHED, + finalState.isTaskbarStashed()); + applyState(); + } + }; + + public void init(TaskbarControllers controllers, BaseQuickstepLauncher launcher) { + mControllers = controllers; + mLauncher = launcher; + + mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController + .getTaskbarBackgroundAlpha(); + MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha(); + mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME); + mIconAlphaForHome.setConsumer( + (Consumer<Float>) alpha -> mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1)); + + mIconAlignmentForResumedState.finishAnimation(); + onIconAlignmentRatioChanged(); + + mLauncher.getStateManager().addStateListener(mStateListener); + } + + public void onDestroy() { + mIconAlignmentForResumedState.finishAnimation(); + mIconAlignmentForGestureState.finishAnimation(); + mIconAlignmentForLauncherState.finishAnimation(); + + mLauncher.getHotseat().setIconsAlpha(1f); + mLauncher.getStateManager().removeStateListener(mStateListener); + } + + public Animator createAnimToLauncher(@NonNull LauncherState toState, + @NonNull RecentsAnimationCallbacks callbacks, long duration) { + // If going to overview, stash the task bar + // If going home, align the icons to hotseat + AnimatorSet animatorSet = new AnimatorSet(); + + TaskbarStashController stashController = mControllers.taskbarStashController; + stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, + toState.isTaskbarStashed()); + stashController.updateStateForFlag(FLAG_IN_APP, false); + + updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, true); + animatorSet.play(stashController.applyStateWithoutStart(duration)); + animatorSet.play(applyState(duration, false)); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mTargetStateOverride = null; + animator.removeListener(this); + } + + @Override + public void onAnimationStart(Animator animator) { + mTargetStateOverride = toState; + } + }); + + TaskBarRecentsAnimationListener listener = new TaskBarRecentsAnimationListener(callbacks); + callbacks.addListener(listener); + RecentsView recentsView = mLauncher.getOverviewPanel(); + recentsView.setTaskLaunchListener(() -> { + listener.endGestureStateOverride(true); + callbacks.removeListener(listener); + }); + return animatorSet; + } + + public boolean isAnimatingToLauncher() { + return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture; + } + + /** + * Updates the proper flag to change the state of the task bar. + * + * Note that this only updates the flag. {@link #applyState()} needs to be called separately. + * + * @param flag The flag to update. + * @param enabled Whether to enable the flag + */ + public void updateStateForFlag(int flag, boolean enabled) { + if (enabled) { + mState |= flag; + } else { + mState &= ~flag; + } + } + + private boolean hasAnyFlag(int flagMask) { + return hasAnyFlag(mState, flagMask); + } + + private boolean hasAnyFlag(int flags, int flagMask) { + return (flags & flagMask) != 0; + } + + public void applyState() { + applyState(TASKBAR_STASH_DURATION); + } + + public void applyState(long duration) { + applyState(duration, true); + } + + public Animator applyState(boolean start) { + return applyState(TASKBAR_STASH_DURATION, start); + } + + public Animator applyState(long duration, boolean start) { + Animator animator = null; + if (mPrevState != mState) { + int changedFlags = mPrevState ^ mState; + animator = onStateChangeApplied(changedFlags, duration, start); + mPrevState = mState; + } + return animator; + } + + private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) { + AnimatorSet animatorSet = new AnimatorSet(); + if (hasAnyFlag(changedFlags, FLAG_RESUMED)) { + boolean isResumed = isResumed(); + ObjectAnimator anim = mIconAlignmentForResumedState + .animateToValue(getCurrentIconAlignmentRatio(), isResumed ? 1 : 0) + .setDuration(duration); + + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mIsAnimatingToLauncherViaResume = false; + } + + @Override + public void onAnimationStart(Animator animation) { + mIsAnimatingToLauncherViaResume = isResumed; + + TaskbarStashController stashController = mControllers.taskbarStashController; + stashController.updateStateForFlag(FLAG_IN_APP, !isResumed); + stashController.applyState(duration); + } + }); + animatorSet.play(anim); + } + + if (hasAnyFlag(changedFlags, FLAG_RECENTS_ANIMATION_RUNNING)) { + boolean isRecentsAnimationRunning = isRecentsAnimationRunning(); + Animator animator = mIconAlignmentForGestureState + .animateToValue(isRecentsAnimationRunning ? 1 : 0); + if (isRecentsAnimationRunning) { + animator.setDuration(duration); + } + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mIsAnimatingToLauncherViaGesture = false; + } + + @Override + public void onAnimationStart(Animator animation) { + mIsAnimatingToLauncherViaGesture = isRecentsAnimationRunning(); + } + }); + animatorSet.play(animator); + } + + if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_START_STASHED)) { + playStateTransitionAnim(isTransitionStateStartStashed(), animatorSet, duration, + false /* committed */); + } + + if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_COMMITTED_STASHED)) { + playStateTransitionAnim(isTransitionStateCommittedStashed(), animatorSet, duration, + true /* committed */); + } + + if (start) { + animatorSet.start(); + } + return animatorSet; + } + + private void playStateTransitionAnim(boolean isTransitionStateStashed, + AnimatorSet animatorSet, long duration, boolean committed) { + TaskbarStashController controller = mControllers.taskbarStashController; + controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, + isTransitionStateStashed); + Animator stashAnimator = controller.applyStateWithoutStart(duration); + if (stashAnimator != null) { + stashAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (isTransitionStateStashed && committed) { + // Reset hotseat alpha to default + mLauncher.getHotseat().setIconsAlpha(1); + } + mTargetStateOverrideForStateTransition = null; + } + + @Override + public void onAnimationStart(Animator animation) { + mIconAlphaForHome.setValue(mLauncher.getHotseat().getIconsAlpha()); + } + }); + animatorSet.play(stashAnimator); + animatorSet.play(mIconAlignmentForLauncherState.animateToValue( + getCurrentIconAlignmentRatioForLauncherState(), + isTransitionStateStashed ? 0 : 1)); + } else { + mTargetStateOverrideForStateTransition = null; + } + } + + private boolean isResumed() { + return (mState & FLAG_RESUMED) != 0; + } + + private boolean isRecentsAnimationRunning() { + return (mState & FLAG_RECENTS_ANIMATION_RUNNING) != 0; + } + + private boolean isTransitionStateStartStashed() { + return (mState & FLAG_TRANSITION_STATE_START_STASHED) != 0; + } + + private boolean isTransitionStateCommittedStashed() { + return (mState & FLAG_TRANSITION_STATE_COMMITTED_STASHED) != 0; + } + + private void onIconAlignmentRatioChangedForStateTransition() { + onIconAlignmentRatioChanged( + mTargetStateOverrideForStateTransition != null + ? mTargetStateOverrideForStateTransition + : mLauncher.getStateManager().getState(), + this::getCurrentIconAlignmentRatioForLauncherState); + } + + private void onIconAlignmentRatioChanged() { + onIconAlignmentRatioChanged(mTargetStateOverride != null ? mTargetStateOverride + : mLauncher.getStateManager().getState(), this::getCurrentIconAlignmentRatio); + } + + private void onIconAlignmentRatioChanged(LauncherState state, + Supplier<Float> alignmentSupplier) { + if (mControllers == null) { + return; + } + float alignment = alignmentSupplier.get(); + mControllers.taskbarViewController.setLauncherIconAlignment( + alignment, mLauncher.getDeviceProfile()); + + mTaskbarBackgroundAlpha.updateValue(1 - alignment); + + setIconAlpha(state, alignment); + } + + private float getCurrentIconAlignmentRatio() { + return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value); + } + + private float getCurrentIconAlignmentRatioForLauncherState() { + return mIconAlignmentForLauncherState.value; + } + + private void setIconAlpha(LauncherState state, float progress) { + if ((state.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) { + // If the hotseat icons are visible, then switch taskbar in last frame + setTaskbarViewVisible(progress < 1); + } else { + mIconAlphaForHome.setValue(1 - progress); + } + } + + private void setTaskbarViewVisible(boolean isVisible) { + mIconAlphaForHome.setValue(isVisible ? 1 : 0); + } + + private final class TaskBarRecentsAnimationListener implements + RecentsAnimationCallbacks.RecentsAnimationListener { + private final RecentsAnimationCallbacks mCallbacks; + + TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) { + mCallbacks = callbacks; + } + + @Override + public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { + endGestureStateOverride(true); + } + + @Override + public void onRecentsAnimationFinished(RecentsAnimationController controller) { + endGestureStateOverride(!controller.getFinishTargetIsLauncher()); + } + + private void endGestureStateOverride(boolean finishedToApp) { + mCallbacks.removeListener(this); + updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, false); + applyState(); + + TaskbarStashController controller = mControllers.taskbarStashController; + controller.updateStateForFlag(FLAG_IN_APP, finishedToApp); + controller.applyState(); + } + } +} |