Files
lawnchair/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
Tony Wickham 24675d36b7 Improve quick switch from home to taskbar
- Add LauncherState#isTaskbarAlignedWithHotseat() which defaults to !isTaskbarStashed(), but is always false for quick switch from home
- Replaced FLAG_TRANSITION_STATE_START_STASHED and FLAG_TRANSITION_STATE_COMMITTED_STASHED with FLAG_STATE_TRANSITION_RUNNING and a reference to mLauncherState. STATE_START is equivalent to TRANSITION_RUNNING changing to true, and STATE_COMMITTED is equivalent to TRANSITION_RUNNING changing to false. Then can get details from the state such as whether taskbar is stashed and icon alignment from mLauncherState

Test: quick switch from home, both with taskbar stashed in apps and not
Fixes: 194728611
Bug: 204657916
Change-Id: I6cf84ec73a4036e14cc7268667c6f62100884c27
2021-12-07 13:13:48 -08:00

391 lines
16 KiB
Java

/*
* 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.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_RUNNING = 1 << 2;
/** Equivalent to an int with all 1s for binary operation purposes */
private static final int FLAGS_ALL = ~0;
private final AnimatedFloat mIconAlignmentForResumedState =
new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
private final AnimatedFloat mIconAlignmentForGestureState =
new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
private final AnimatedFloat mIconAlignmentForLauncherState =
new AnimatedFloat(this::onIconAlignmentRatioChangedForStateTransition);
private TaskbarControllers mControllers;
private AnimatedFloat mTaskbarBackgroundAlpha;
private MultiValueAlpha.AlphaProperty mIconAlphaForHome;
private BaseQuickstepLauncher mLauncher;
private Integer mPrevState;
private int mState;
private LauncherState mLauncherState = LauncherState.NORMAL;
private boolean mIsAnimatingToLauncherViaGesture;
private boolean mIsAnimatingToLauncherViaResume;
private final StateManager.StateListener<LauncherState> mStateListener =
new StateManager.StateListener<LauncherState>() {
@Override
public void onStateTransitionStart(LauncherState toState) {
if (toState != mLauncherState) {
// Treat FLAG_TRANSITION_STATE_RUNNING as a changed flag even if a previous
// state transition was already running, so we update the new target.
mPrevState &= ~FLAG_TRANSITION_STATE_RUNNING;
mLauncherState = toState;
}
updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, true);
applyState();
}
@Override
public void onStateTransitionComplete(LauncherState finalState) {
mLauncherState = finalState;
updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, false);
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();
onIconAlignmentRatioChangedForAppAndHomeTransition();
mLauncher.getStateManager().addStateListener(mStateListener);
}
public void onDestroy() {
mIconAlignmentForResumedState.finishAnimation();
mIconAlignmentForGestureState.finishAnimation();
mIconAlignmentForLauncherState.finishAnimation();
mIconAlphaForHome.setConsumer(null);
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();
// Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
TaskbarStashController stashController = mControllers.taskbarStashController;
stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
toState.isTaskbarStashed(mLauncher));
stashController.updateStateForFlag(FLAG_IN_APP, false);
updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, true);
animatorSet.play(stashController.applyStateWithoutStart(duration));
animatorSet.play(applyState(duration, false));
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 == null || mPrevState != mState) {
// If this is our initial state, treat all flags as changed.
int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState;
mPrevState = mState;
animator = onStateChangeApplied(changedFlags, duration, start);
}
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(isResumed && goingToUnstashedLauncherState()
? 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 && goingToUnstashedLauncherState()
? 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_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING)) {
boolean goingToLauncher = hasAnyFlag(FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING);
animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(goingToLauncher ? 0 : 1)
.setDuration(duration));
}
if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_RUNNING)) {
boolean committed = !hasAnyFlag(FLAG_TRANSITION_STATE_RUNNING);
playStateTransitionAnim(animatorSet, duration, committed);
if (committed && mLauncherState == LauncherState.QUICK_SWITCH) {
// We're about to be paused, set immediately to ensure seamless handoff.
updateStateForFlag(FLAG_RESUMED, false);
applyState(0 /* duration */);
}
}
if (start) {
animatorSet.start();
}
return animatorSet;
}
/** Returns whether we're going to a state where taskbar icons should align with launcher. */
private boolean goingToUnstashedLauncherState() {
return !mControllers.taskbarStashController.isInStashedLauncherState();
}
private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
boolean committed) {
boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
float toAlignment = mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) ? 1 : 0;
TaskbarStashController controller = mControllers.taskbarStashController;
controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
Animator stashAnimator = controller.applyStateWithoutStart(duration);
if (stashAnimator != null) {
stashAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (isInStashedState && committed) {
// Reset hotseat alpha to default
mLauncher.getHotseat().setIconsAlpha(1);
}
}
@Override
public void onAnimationStart(Animator animation) {
if (mLauncher.getHotseat().getIconsAlpha() > 0) {
mIconAlphaForHome.setValue(mLauncher.getHotseat().getIconsAlpha());
}
}
});
animatorSet.play(stashAnimator);
}
animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
.setDuration(duration));
}
private boolean isResumed() {
return (mState & FLAG_RESUMED) != 0;
}
private boolean isRecentsAnimationRunning() {
return (mState & FLAG_RECENTS_ANIMATION_RUNNING) != 0;
}
private void onIconAlignmentRatioChangedForStateTransition() {
if (!isResumed()) {
return;
}
onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioForLauncherState);
}
private void onIconAlignmentRatioChangedForAppAndHomeTransition() {
onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioBetweenAppAndHome);
}
private void onIconAlignmentRatioChanged(Supplier<Float> alignmentSupplier) {
if (mControllers == null) {
return;
}
float alignment = alignmentSupplier.get();
mControllers.taskbarViewController.setLauncherIconAlignment(
alignment, mLauncher.getDeviceProfile());
// Switch taskbar and hotseat in last frame
setTaskbarViewVisible(alignment < 1);
}
private float getCurrentIconAlignmentRatioBetweenAppAndHome() {
return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
}
private float getCurrentIconAlignmentRatioForLauncherState() {
return mIconAlignmentForLauncherState.value;
}
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);
// Update the resumed state immediately to ensure a seamless handoff
boolean launcherResumed = !finishedToApp;
mIconAlignmentForResumedState.updateValue(launcherResumed ? 1 : 0);
updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, false);
updateStateForFlag(FLAG_RESUMED, launcherResumed);
applyState();
TaskbarStashController controller = mControllers.taskbarStashController;
controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
controller.applyState();
}
}
}