diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index aa312619bb..1c0c77328f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -15,18 +15,10 @@ */ package com.android.launcher3.taskbar; -import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; -import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; -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 static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED; import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.annotation.ColorInt; import android.graphics.Rect; import android.os.RemoteException; @@ -44,28 +36,17 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.anim.AnimatorListeners; -import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.statemanager.StateManager; -import com.android.launcher3.util.MultiValueAlpha; -import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.util.OnboardingPrefs; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.RecentsAnimationCallbacks; -import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; -import com.android.quickstep.RecentsAnimationController; -import com.android.quickstep.views.RecentsView; -import com.android.systemui.shared.recents.model.ThumbnailData; import java.util.Arrays; -import java.util.HashMap; import java.util.Set; -import java.util.function.Supplier; import java.util.stream.Stream; /** @@ -77,82 +58,15 @@ public class LauncherTaskbarUIController extends TaskbarUIController { private final BaseQuickstepLauncher mLauncher; - 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 final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener = this::onStashedInAppChanged; - private final StateManager.StateListener mStateListener = - new StateManager.StateListener() { - private Animator mAnimator; - - @Override - public void onStateTransitionStart(LauncherState toState) { - // Stash animation from going to launcher should be already handled in - // createAnimToLauncher. - TaskbarStashController controller = mControllers.taskbarStashController; - long duration = TASKBAR_STASH_DURATION; - controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, - toState.isTaskbarStashed()); - Animator stashAnimator = controller.applyStateWithoutStart(duration); - if (stashAnimator != null) { - if (mAnimator != null) { - mAnimator.cancel(); - } - PendingAnimation pendingAnimation = new PendingAnimation(duration); - pendingAnimation.add(stashAnimator); - pendingAnimation.setFloat(mIconAlignmentForLauncherState, - AnimatedFloat.VALUE, toState.isTaskbarStashed() ? 0 : 1, - FAST_OUT_SLOW_IN); - pendingAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animator) { - mTargetStateOverrideForStateTransition = toState; - // Copy hotseat alpha over to taskbar icons - mIconAlphaForHome.setValue(mLauncher.getHotseat().getIconsAlpha()); - mLauncher.getHotseat().setIconsAlpha(0); - } - - @Override - public void onAnimationEnd(Animator animator) { - if (toState.isTaskbarStashed()) { - // Reset hotseat alpha to default - mLauncher.getHotseat().setIconsAlpha(1); - } - mTargetStateOverrideForStateTransition = null; - mAnimator = null; - } - }); - mAnimator = pendingAnimation.buildAnim(); - mAnimator.start(); - } - } - - @Override - public void onStateTransitionComplete(LauncherState finalState) { - TaskbarStashController controller = mControllers.taskbarStashController; - controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, - finalState.isTaskbarStashed()); - controller.applyState(); - } - }; - // Initialized in init. private TaskbarControllers mControllers; - private AnimatedFloat mTaskbarBackgroundAlpha; private AnimatedFloat mTaskbarOverrideBackgroundAlpha; - private AlphaProperty mIconAlphaForHome; - private boolean mIsAnimatingToLauncherViaResume; - private boolean mIsAnimatingToLauncherViaGesture; private TaskbarKeyguardController mKeyguardController; - - private LauncherState mTargetStateOverride = null; - private LauncherState mTargetStateOverrideForStateTransition = null; + private final TaskbarLauncherStateController + mTaskbarLauncherStateController = new TaskbarLauncherStateController(); private final DeviceProfile.OnDeviceProfileChangeListener mProfileChangeListener = new DeviceProfile.OnDeviceProfileChangeListener() { @@ -171,37 +85,26 @@ public class LauncherTaskbarUIController extends TaskbarUIController { protected void init(TaskbarControllers taskbarControllers) { mControllers = taskbarControllers; - mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController - .getTaskbarBackgroundAlpha(); + mTaskbarLauncherStateController.init(mControllers, mLauncher); mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController .getOverrideBackgroundAlpha(); - MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha(); - mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME); - mLauncher.setTaskbarUIController(this); mKeyguardController = taskbarControllers.taskbarKeyguardController; onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */); - mIconAlignmentForResumedState.finishAnimation(); - onIconAlignmentRatioChanged(); onStashedInAppChanged(mLauncher.getDeviceProfile()); mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); - mLauncher.getStateManager().addStateListener(mStateListener); mLauncher.addOnDeviceProfileChangeListener(mProfileChangeListener); } @Override protected void onDestroy() { onLauncherResumedOrPaused(false); - mIconAlignmentForResumedState.finishAnimation(); - mIconAlignmentForGestureState.finishAnimation(); - mIconAlignmentForLauncherState.finishAnimation(); + mTaskbarLauncherStateController.onDestroy(); mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); - mLauncher.getStateManager().removeStateListener(mStateListener); - mLauncher.getHotseat().setIconsAlpha(1f); mLauncher.setTaskbarUIController(null); mLauncher.removeOnDeviceProfileChangeListener(mProfileChangeListener); updateTaskTransitionSpec(true); @@ -209,11 +112,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController { @Override protected boolean isTaskbarTouchable() { - return !isAnimatingToLauncher(); - } - - private boolean isAnimatingToLauncher() { - return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture; + return !mTaskbarLauncherStateController.isAnimatingToLauncher(); } @Override @@ -240,24 +139,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } } - long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION; - if (fromInit) { - // Since we are creating the starting state, we don't have a state to animate from, so - // set our state immediately. - duration = 0; - } - ObjectAnimator anim = mIconAlignmentForResumedState.animateToValue( - getCurrentIconAlignmentRatio(), isResumed ? 1 : 0) - .setDuration(duration); - - anim.addListener(AnimatorListeners.forEndCallback( - () -> mIsAnimatingToLauncherViaResume = false)); - anim.start(); - mIsAnimatingToLauncherViaResume = isResumed; - - TaskbarStashController stashController = mControllers.taskbarStashController; - stashController.updateStateForFlag(FLAG_IN_APP, !isResumed); - stashController.applyState(duration); + mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed); + mTaskbarLauncherStateController.applyState( + fromInit ? 0 : QuickstepTransitionManager.CONTENT_ALPHA_DURATION); } /** @@ -268,77 +152,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController { */ public Animator createAnimToLauncher(@NonNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration) { - AnimatorSet animatorSet = new AnimatorSet(); - TaskbarStashController stashController = mControllers.taskbarStashController; - stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, - toState.isTaskbarStashed()); - if (toState.isTaskbarStashed()) { - animatorSet.play(stashController.applyStateWithoutStart(duration)); - } else { - animatorSet.play(mIconAlignmentForGestureState - .animateToValue(1) - .setDuration(duration)); - } - animatorSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - mTargetStateOverride = null; - animator.removeListener(this); - } - - @Override - public void onAnimationStart(Animator animator) { - mTargetStateOverride = toState; - mIsAnimatingToLauncherViaGesture = true; - stashController.updateStateForFlag(FLAG_IN_APP, false); - stashController.applyState(duration); - } - }); - - TaskBarRecentsAnimationListener listener = new TaskBarRecentsAnimationListener(callbacks); - callbacks.addListener(listener); - RecentsView recentsView = mLauncher.getOverviewPanel(); - recentsView.setTaskLaunchListener(() -> { - listener.endGestureStateOverride(true); - callbacks.removeListener(listener); - }); - - return animatorSet; - } - - private float getCurrentIconAlignmentRatio() { - return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value); - } - - private float getCurrentIconAlignmentRatioForLauncherState() { - return mIconAlignmentForLauncherState.value; - } - - 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 alignmentSupplier) { - if (mControllers == null) { - return; - } - float alignment = alignmentSupplier.get(); - mControllers.taskbarViewController.setLauncherIconAlignment( - alignment, mLauncher.getDeviceProfile()); - - mTaskbarBackgroundAlpha.updateValue(1 - alignment); - - setIconAlpha(state, alignment); + return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration); } /** @@ -358,20 +172,6 @@ public class LauncherTaskbarUIController extends TaskbarUIController { return mControllers.taskbarActivityContext.getDragLayer(); } - 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); - mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f); - } - @Override protected void onStashedInAppChanged() { onStashedInAppChanged(mLauncher.getDeviceProfile()); @@ -451,35 +251,4 @@ public class LauncherTaskbarUIController extends TaskbarUIController { mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item, instanceId); } - - private final class TaskBarRecentsAnimationListener implements RecentsAnimationListener { - private final RecentsAnimationCallbacks mCallbacks; - - TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) { - mCallbacks = callbacks; - } - - @Override - public void onRecentsAnimationCanceled(HashMap thumbnailDatas) { - endGestureStateOverride(true); - } - - @Override - public void onRecentsAnimationFinished(RecentsAnimationController controller) { - endGestureStateOverride(!controller.getFinishTargetIsLauncher()); - } - - private void endGestureStateOverride(boolean finishedToApp) { - mCallbacks.removeListener(this); - mIsAnimatingToLauncherViaGesture = false; - - mIconAlignmentForGestureState - .animateToValue(0) - .start(); - - TaskbarStashController controller = mControllers.taskbarStashController; - controller.updateStateForFlag(FLAG_IN_APP, finishedToApp); - controller.applyState(); - } - } } 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 mStateListener = + new StateManager.StateListener() { + + @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) 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 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 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(); + } + } +} diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 26d935d501..75e8dd1f7a 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -117,11 +117,12 @@ public class OverviewCommandHelper { mPendingCommands.clear(); } + @Nullable private TaskView getNextTask(RecentsView view) { final TaskView runningTaskView = view.getRunningTaskView(); if (runningTaskView == null) { - return view.getTaskViewCount() > 0 ? view.getTaskViewAt(0) : null; + return view.getTaskViewAt(0); } else { final TaskView nextTask = view.getNextTaskView(); return nextTask != null ? nextTask : runningTaskView; @@ -256,8 +257,8 @@ public class OverviewCommandHelper { // Ensure that recents view has focus so that it receives the followup key inputs TaskView taskView = rv.getNextTaskView(); if (taskView == null) { - if (rv.getTaskViewCount() > 0) { - taskView = rv.getTaskViewAt(0); + taskView = rv.getTaskViewAt(0); + if (taskView != null) { taskView.requestFocus(); } else { rv.requestFocus(); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 223ce1c556..0188962da9 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -383,7 +383,9 @@ public abstract class RecentsView mSizeStrategy; + @Nullable protected RecentsAnimationController mRecentsAnimationController; + @Nullable protected SurfaceTransactionApplier mSyncTransactionApplier; protected int mTaskWidth; protected int mTaskHeight; @@ -394,12 +396,15 @@ public abstract class RecentsView extends IPipAnimationListener.Stub { + @Nullable private T mActivity; + @Nullable private RecentsView mRecentsView; - public void setActivityAndRecentsView(T activity, RecentsView recentsView) { + public void setActivityAndRecentsView(@Nullable T activity, + @Nullable RecentsView recentsView) { mActivity = activity; mRecentsView = recentsView; } diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java index c960f88437..f8368aed6c 100644 --- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java @@ -459,15 +459,36 @@ public class TaskThumbnailView extends View { float availableHeight = surfaceHeight - (thumbnailClipHint.top + thumbnailClipHint.bottom); - if (isRotated) { - float canvasAspect = canvasWidth / (float) canvasHeight; - float availableAspect = availableHeight / availableWidth; + float canvasAspect = canvasWidth / (float) canvasHeight; + float availableAspect = isRotated + ? availableHeight / availableWidth + : availableWidth / availableHeight; + boolean isAspectLargelyDifferent = Utilities.isRelativePercentDifferenceGreaterThan( + canvasAspect, availableAspect, 0.1f); + if (isRotated && isAspectLargelyDifferent) { // Do not rotate thumbnail if it would not improve fit - if (Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect, - availableAspect, 0.1f)) { - isRotated = false; - isOrientationDifferent = false; + isRotated = false; + isOrientationDifferent = false; + } + + if (isAspectLargelyDifferent) { + // Crop letterbox insets if insets isn't already clipped + if (!TaskView.clipLeft(dp)) { + thumbnailClipHint.left = thumbnailData.letterboxInsets.left; } + if (!TaskView.clipRight(dp)) { + thumbnailClipHint.right = thumbnailData.letterboxInsets.right; + } + if (!TaskView.clipTop(dp)) { + thumbnailClipHint.top = thumbnailData.letterboxInsets.top; + } + if (!TaskView.clipBottom(dp)) { + thumbnailClipHint.bottom = thumbnailData.letterboxInsets.bottom; + } + availableWidth = surfaceWidth + - (thumbnailClipHint.left + thumbnailClipHint.right); + availableHeight = surfaceHeight + - (thumbnailClipHint.top + thumbnailClipHint.bottom); } final float targetW, targetH; @@ -478,30 +499,25 @@ public class TaskThumbnailView extends View { targetW = canvasWidth; targetH = canvasHeight; } - float canvasAspect = targetW / targetH; + float targetAspect = targetW / targetH; // Update the clipHint such that // > the final clipped position has same aspect ratio as requested by canvas - // > the clipped region is within the task insets if possible - // > the clipped region is not scaled up when drawing. If that is not possible - // while staying within the taskInsets, move outside the insets. + // > first fit the width and crop the extra height + // > if that will leave empty space, fit the height and crop the width instead float croppedWidth = availableWidth; - if (croppedWidth < targetW) { - croppedWidth = Math.min(targetW, surfaceWidth); - } - - float croppedHeight = croppedWidth / canvasAspect; + float croppedHeight = croppedWidth / targetAspect; if (croppedHeight > availableHeight) { croppedHeight = availableHeight; if (croppedHeight < targetH) { croppedHeight = Math.min(targetH, surfaceHeight); } - croppedWidth = croppedHeight * canvasAspect; + croppedWidth = croppedHeight * targetAspect; // One last check in case the task aspect radio messed up something if (croppedWidth > surfaceWidth) { croppedWidth = surfaceWidth; - croppedHeight = croppedWidth / canvasAspect; + croppedHeight = croppedWidth / targetAspect; } } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 9f3d445562..68e19cb85d 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -383,7 +383,7 @@ public class LauncherProvider extends ContentProvider { case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: { Bundle result = new Bundle(); result.putInt(LauncherSettings.Settings.EXTRA_VALUE, - mOpenHelper.generateNewScreenId()); + mOpenHelper.getNewScreenId()); return result; } case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: { @@ -628,7 +628,6 @@ public class LauncherProvider extends ContentProvider { private final Context mContext; private final boolean mForMigration; private int mMaxItemId = -1; - private int mMaxScreenId = -1; private boolean mBackupTableExists; private boolean mHotseatRestoreTableExists; @@ -672,9 +671,6 @@ public class LauncherProvider extends ContentProvider { if (mMaxItemId == -1) { mMaxItemId = initializeMaxItemId(getWritableDatabase()); } - if (mMaxScreenId == -1) { - mMaxScreenId = initializeMaxScreenId(getWritableDatabase()); - } } @Override @@ -682,7 +678,6 @@ public class LauncherProvider extends ContentProvider { if (LOGD) Log.d(TAG, "creating new launcher database"); mMaxItemId = 1; - mMaxScreenId = 0; addFavoritesTable(db, false); @@ -1043,36 +1038,19 @@ public class LauncherProvider extends ContentProvider { public void checkId(ContentValues values) { int id = values.getAsInteger(Favorites._ID); mMaxItemId = Math.max(id, mMaxItemId); - - Integer screen = values.getAsInteger(Favorites.SCREEN); - Integer container = values.getAsInteger(Favorites.CONTAINER); - if (screen != null && container != null - && container.intValue() == Favorites.CONTAINER_DESKTOP) { - mMaxScreenId = Math.max(screen, mMaxScreenId); - } } private int initializeMaxItemId(SQLiteDatabase db) { return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s", Favorites._ID, Favorites.TABLE_NAME); } - // Generates a new ID to use for an workspace screen in your database. This method - // should be only called from the main UI thread. As an exception, we do call it when we - // call the constructor from the worker thread; however, this doesn't extend until after the - // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp - // after that point - public int generateNewScreenId() { - if (mMaxScreenId < 0) { - throw new RuntimeException("Error: max screen id was not initialized"); - } - mMaxScreenId += 1; - return mMaxScreenId; - } - - private int initializeMaxScreenId(SQLiteDatabase db) { - return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0", + // Returns a new ID to use for an workspace screen in your database that is greater than all + // existing screen IDs. + private int getNewScreenId() { + return getMaxId(getWritableDatabase(), + "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0", Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER, - Favorites.CONTAINER_DESKTOP); + Favorites.CONTAINER_DESKTOP) + 1; } @Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) { @@ -1081,7 +1059,6 @@ public class LauncherProvider extends ContentProvider { // Ensure that the max ids are initialized mMaxItemId = initializeMaxItemId(db); - mMaxScreenId = initializeMaxScreenId(db); return count; } } diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 523ac726f9..cefadf7941 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -1721,6 +1721,10 @@ public abstract class PagedView extends ViewGrou @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { + if (mScroller.isFinished()) { + // This was not caused by the scroller, skip it. + return; + } int newDestinationPage = getDestinationPage(); if (newDestinationPage >= 0 && newDestinationPage != mCurrentScrollOverPage) { mCurrentScrollOverPage = newDestinationPage; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index ce06c6e1fd..131fbfb071 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -869,13 +869,13 @@ public class Workspace extends PagedView mWorkspaceScreens.remove(emptyScreenId); mScreenOrder.removeValue(emptyScreenId); - int newScreenId = -1; + int newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(), + LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) + .getInt(LauncherSettings.Settings.EXTRA_VALUE); // Launcher database isn't aware of empty pages that are already bound, so we need to // skip those IDs manually. - while (newScreenId == -1 || mWorkspaceScreens.containsKey(newScreenId)) { - newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(), - LauncherSettings.Settings.METHOD_NEW_SCREEN_ID) - .getInt(LauncherSettings.Settings.EXTRA_VALUE); + while (mWorkspaceScreens.containsKey(newScreenId)) { + newScreenId++; } mWorkspaceScreens.put(newScreenId, cl); diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index fea15c41f8..a13fa55dff 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -16,7 +16,6 @@ package com.android.launcher3.model; import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID; -import static com.android.launcher3.WorkspaceLayoutManager.SECOND_SCREEN_ID; import android.content.Intent; import android.content.pm.LauncherActivityInfo; @@ -300,11 +299,6 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { IntSet screensToExclude = new IntSet(); if (FeatureFlags.QSB_ON_FIRST_SCREEN) { screensToExclude.add(FIRST_SCREEN_ID); - - // On split display we don't want to add the new items onto the second screen. - if (app.getInvariantDeviceProfile().isSplitDisplay) { - screensToExclude.add(SECOND_SCREEN_ID); - } } for (int screen = 0; screen < screenCount; screen++) { diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index 0439e75843..94e06d16ce 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -292,7 +292,7 @@ public class ModelWriter { FileLog.d(TAG, "removing items from db " + items.stream().map( (item) -> item.getTargetComponent() == null ? "" : item.getTargetComponent().getPackageName()).collect( - Collectors.joining(",")), new Exception()); + Collectors.joining(","))); notifyDelete(items); enqueueDeleteRunnable(() -> { for (ItemInfo item : items) { diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java index bd39391f44..326141d66e 100644 --- a/src/com/android/launcher3/util/MultiValueAlpha.java +++ b/src/com/android/launcher3/util/MultiValueAlpha.java @@ -24,6 +24,7 @@ import android.view.View; import com.android.launcher3.anim.AlphaUpdateListener; import java.util.Arrays; +import java.util.function.Consumer; /** * Utility class to handle separating a single value as a factor of multiple values @@ -85,6 +86,8 @@ public class MultiValueAlpha { // Factor of all other alpha channels, only valid if mMyMask is present in mValidMask. private float mOthers = 1; + private Consumer mConsumer; + AlphaProperty(int myMask) { mMyMask = myMask; } @@ -109,16 +112,24 @@ public class MultiValueAlpha { mValidMask = mMyMask; mValue = value; - mView.setAlpha(mOthers * mValue); + final float alpha = mOthers * mValue; + mView.setAlpha(alpha); if (mUpdateVisibility) { AlphaUpdateListener.updateVisibility(mView); } + if (mConsumer != null) { + mConsumer.accept(mValue); + } } public float getValue() { return mValue; } + public void setConsumer(Consumer consumer) { + mConsumer = consumer; + } + @Override public String toString() { return Float.toString(mValue); diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java index 16f024e421..8a4590a388 100644 --- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java +++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java @@ -86,8 +86,6 @@ public class AddWorkspaceItemsTaskTest { @Test public void testFindSpaceForItem_prefers_second() throws Exception { - mIdp.isSplitDisplay = false; - // First screen has only one hole of size 1 int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); @@ -108,24 +106,6 @@ public class AddWorkspaceItemsTaskTest { .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3)); } - @Test - public void testFindSpaceForItem_prefers_third_on_split_display() throws Exception { - mIdp.isSplitDisplay = true; - // First screen has only one hole of size 1 - int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); - - // Second screen has 2 holes of sizes 3x2 and 2x3 - setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5)); - - int[] spaceFound = newTask().findSpaceForItem( - mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1); - // For split display, it picks the next screen, even if there is enough space - // on previous screen - assertEquals(2, spaceFound[0]); - assertTrue(mScreenOccupancy.get(spaceFound[0]) - .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1)); - } - @Test public void testFindSpaceForItem_adds_new_screen() throws Exception { // First screen has 2 holes of sizes 3x2 and 2x3