diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml index d61a8952a7..dfa17d6cc4 100644 --- a/quickstep/res/layout/taskbar.xml +++ b/quickstep/res/layout/taskbar.xml @@ -15,6 +15,7 @@ --> + + \ No newline at end of file diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index d8899a6e8e..4f62b34a5f 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -153,4 +153,7 @@ 16dp 16dp 48dp + 24dp + 220dp + 6dp diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 7d0afe1aba..c98bc87168 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -50,21 +50,23 @@ public class LauncherTaskbarUIController extends TaskbarUIController { private final TaskbarHotseatController mHotseatController; private final TaskbarActivityContext mContext; - final TaskbarDragLayer mTaskbarDragLayer; - final TaskbarView mTaskbarView; + private final TaskbarDragLayer mTaskbarDragLayer; + private final TaskbarView mTaskbarView; private final AnimatedFloat mIconAlignmentForResumedState = new AnimatedFloat(this::onIconAlignmentRatioChanged); private final AnimatedFloat mIconAlignmentForGestureState = new AnimatedFloat(this::onIconAlignmentRatioChanged); + // Initialized in init. + private TaskbarControllers mControllers; private AnimatedFloat mTaskbarBackgroundAlpha; private AlphaProperty mIconAlphaForHome; - private boolean mIsAnimatingToLauncher; + private boolean mIsAnimatingToLauncherViaResume; + private boolean mIsAnimatingToLauncherViaGesture; private TaskbarKeyguardController mKeyguardController; private LauncherState mTargetStateOverride = null; - private TaskbarControllers mControllers; public LauncherTaskbarUIController( BaseQuickstepLauncher launcher, TaskbarActivityContext context) { @@ -80,13 +82,14 @@ public class LauncherTaskbarUIController extends TaskbarUIController { @Override protected void init(TaskbarControllers taskbarControllers) { - mTaskbarBackgroundAlpha = taskbarControllers.taskbarDragLayerController - .getTaskbarBackgroundAlpha(); - MultiValueAlpha taskbarIconAlpha = taskbarControllers.taskbarViewController - .getTaskbarIconAlpha(); - mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME); mControllers = taskbarControllers; + mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController + .getTaskbarBackgroundAlpha(); + + MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha(); + mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME); + mHotseatController.init(); mLauncher.setTaskbarUIController(this); mKeyguardController = taskbarControllers.taskbarKeyguardController; @@ -109,19 +112,17 @@ public class LauncherTaskbarUIController extends TaskbarUIController { @Override protected boolean isTaskbarTouchable() { - return !mIsAnimatingToLauncher && mTargetStateOverride == null; + return !isAnimatingToLauncher() && !mControllers.taskbarStashController.isStashed(); + } + + private boolean isAnimatingToLauncher() { + return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture; } @Override protected void updateContentInsets(Rect outContentInsets) { - // TaskbarDragLayer provides insets to other apps based on contentInsets. These - // insets should stay consistent even if we expand TaskbarDragLayer's bounds, e.g. - // to show a floating view like Folder. Thus, we set the contentInsets to be where - // mTaskbarView is, since its position never changes and insets rather than overlays. - outContentInsets.left = mTaskbarView.getLeft(); - outContentInsets.top = mTaskbarView.getTop(); - outContentInsets.right = mTaskbarDragLayer.getWidth() - mTaskbarView.getRight(); - outContentInsets.bottom = mTaskbarDragLayer.getHeight() - mTaskbarView.getBottom(); + int contentHeight = mControllers.taskbarStashController.getContentHeight(); + outContentInsets.top = mTaskbarDragLayer.getHeight() - contentHeight; } /** @@ -137,13 +138,20 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } } + long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION; ObjectAnimator anim = mIconAlignmentForResumedState.animateToValue( getCurrentIconAlignmentRatio(), isResumed ? 1 : 0) - .setDuration(QuickstepTransitionManager.CONTENT_ALPHA_DURATION); + .setDuration(duration); - anim.addListener(AnimatorListeners.forEndCallback(() -> mIsAnimatingToLauncher = false)); + anim.addListener(AnimatorListeners.forEndCallback( + () -> mIsAnimatingToLauncherViaResume = false)); anim.start(); - mIsAnimatingToLauncher = isResumed; + mIsAnimatingToLauncherViaResume = isResumed; + + if (!isResumed) { + TaskbarStashController stashController = mControllers.taskbarStashController; + stashController.animateToIsStashed(stashController.isStashedInApp(), duration); + } } /** @@ -155,36 +163,48 @@ public class LauncherTaskbarUIController extends TaskbarUIController { public Animator createAnimToLauncher(@NonNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration) { + TaskbarStashController stashController = mControllers.taskbarStashController; ObjectAnimator animator = mIconAlignmentForGestureState - .animateToValue(mIconAlignmentForGestureState.value, 1) + .animateToValue(1) .setDuration(duration); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mTargetStateOverride = null; + animator.removeListener(this); } @Override public void onAnimationStart(Animator animation) { mTargetStateOverride = toState; + mIsAnimatingToLauncherViaGesture = true; + // TODO: base this on launcher state + stashController.animateToIsStashed(false, duration); } }); callbacks.addListener(new RecentsAnimationListener() { @Override public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { - endGestureStateOverride(); + endGestureStateOverride(true); } @Override public void onRecentsAnimationFinished(RecentsAnimationController controller) { - endGestureStateOverride(); + endGestureStateOverride(!controller.getFinishTargetIsLauncher()); } - private void endGestureStateOverride() { + private void endGestureStateOverride(boolean finishedToApp) { callbacks.removeListener(this); + mIsAnimatingToLauncherViaGesture = false; + mIconAlignmentForGestureState - .animateToValue(mIconAlignmentForGestureState.value, 0) + .animateToValue(0) .start(); + + if (finishedToApp) { + // We only need this for the exiting live tile case. + stashController.animateToIsStashed(stashController.isStashedInApp()); + } } }); return animator; @@ -215,6 +235,11 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } } + @Override + public boolean onLongPressToUnstashTaskbar() { + return mControllers.taskbarStashController.onLongPressToUnstashTaskbar(); + } + /** * Should be called when one or more items in the Hotseat have changed. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java new file mode 100644 index 0000000000..8c14ff6d16 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java @@ -0,0 +1,108 @@ +/* + * 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 android.animation.Animator; +import android.content.res.Resources; +import android.graphics.Outline; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewOutlineProvider; + +import androidx.annotation.Nullable; + +import com.android.launcher3.R; +import com.android.launcher3.anim.RevealOutlineAnimation; +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; +import com.android.quickstep.AnimatedFloat; + +/** + * Handles properties/data collection, then passes the results to our stashed handle View to render. + */ +public class StashedHandleViewController { + + private final TaskbarActivityContext mActivity; + private final View mStashedHandleView; + private final int mStashedHandleWidth; + private final int mStashedHandleHeight; + private final AnimatedFloat mTaskbarStashedHandleAlpha = new AnimatedFloat( + this::updateStashedHandleAlpha); + + // Initialized in init. + private TaskbarControllers mControllers; + + // The bounds we want to clip to in the settled state when showing the stashed handle. + private final Rect mStashedHandleBounds = new Rect(); + private float mStashedHandleRadius; + + private boolean mIsAtStashedRevealBounds = true; + + public StashedHandleViewController(TaskbarActivityContext activity, View stashedHandleView) { + mActivity = activity; + mStashedHandleView = stashedHandleView; + final Resources resources = mActivity.getResources(); + mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width); + mStashedHandleHeight = resources.getDimensionPixelSize( + R.dimen.taskbar_stashed_handle_height); + } + + public void init(TaskbarControllers controllers) { + mControllers = controllers; + mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; + + updateStashedHandleAlpha(); + + final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight(); + mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + final int stashedCenterX = view.getWidth() / 2; + final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2; + mStashedHandleBounds.set( + stashedCenterX - mStashedHandleWidth / 2, + stashedCenterY - mStashedHandleHeight / 2, + stashedCenterX + mStashedHandleWidth / 2, + stashedCenterY + mStashedHandleHeight / 2); + mStashedHandleRadius = view.getHeight() / 2f; + outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius); + } + }); + } + + public AnimatedFloat getStashedHandleAlpha() { + return mTaskbarStashedHandleAlpha; + } + + /** + * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle + * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape + * morphs into the size of where the taskbar icons will be. + */ + public @Nullable Animator createRevealAnimToIsStashed(boolean isStashed) { + if (mIsAtStashedRevealBounds == isStashed) { + return null; + } + mIsAtStashedRevealBounds = isStashed; + final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider( + mStashedHandleRadius, mStashedHandleRadius, + mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds); + return handleRevealProvider.createRevealAnimator(mStashedHandleView, !isStashed); + } + + protected void updateStashedHandleAlpha() { + mStashedHandleView.setAlpha(mTaskbarStashedHandleAlpha.value); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 41426108e5..f4703d3a99 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -115,6 +115,7 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ R.layout.taskbar, null, false); TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view); FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view); + View stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle); // Construct controllers. mControllers = new TaskbarControllers(this, @@ -125,7 +126,9 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ R.color.popup_color_primary_light), new TaskbarDragLayerController(this, mDragLayer), new TaskbarViewController(this, taskbarView), - new TaskbarKeyguardController(this)); + new TaskbarKeyguardController(this), + new StashedHandleViewController(this, stashedHandleView), + new TaskbarStashController(this)); Display display = windowContext.getDisplay(); Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java index c48c28b9e5..8279a470f0 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java @@ -32,6 +32,8 @@ public class TaskbarControllers { public final TaskbarDragLayerController taskbarDragLayerController; public final TaskbarViewController taskbarViewController; public final TaskbarKeyguardController taskbarKeyguardController; + public final StashedHandleViewController stashedHandleViewController; + public final TaskbarStashController taskbarStashController; /** Do not store this controller, as it may change at runtime. */ @NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT; @@ -43,7 +45,9 @@ public class TaskbarControllers { RotationButtonController rotationButtonController, TaskbarDragLayerController taskbarDragLayerController, TaskbarViewController taskbarViewController, - TaskbarKeyguardController taskbarKeyguardController) { + TaskbarKeyguardController taskbarKeyguardController, + StashedHandleViewController stashedHandleViewController, + TaskbarStashController taskbarStashController) { this.taskbarActivityContext = taskbarActivityContext; this.taskbarDragController = taskbarDragController; this.navButtonController = navButtonController; @@ -52,6 +56,8 @@ public class TaskbarControllers { this.taskbarDragLayerController = taskbarDragLayerController; this.taskbarViewController = taskbarViewController; this.taskbarKeyguardController = taskbarKeyguardController; + this.stashedHandleViewController = stashedHandleViewController; + this.taskbarStashController = taskbarStashController; } /** @@ -67,6 +73,8 @@ public class TaskbarControllers { taskbarDragLayerController.init(this); taskbarViewController.init(this); taskbarKeyguardController.init(navbarButtonsViewController); + stashedHandleViewController.init(this); + taskbarStashController.init(this); } /** diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java index ac121ab5fa..cd1baf726d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java @@ -40,10 +40,11 @@ import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInset public class TaskbarDragLayer extends BaseDragLayer { private final Paint mTaskbarBackgroundPaint; + private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets; private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks; - private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets; + private float mTaskbarBackgroundOffset; public TaskbarDragLayer(@NonNull Context context) { this(context, null); @@ -118,8 +119,10 @@ public class TaskbarDragLayer extends BaseDragLayer { @Override protected void dispatchDraw(Canvas canvas) { - canvas.drawRect(0, canvas.getHeight() - mControllerCallbacks.getTaskbarBackgroundHeight(), - canvas.getWidth(), canvas.getHeight(), mTaskbarBackgroundPaint); + float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight() + * (1f - mTaskbarBackgroundOffset); + canvas.drawRect(0, canvas.getHeight() - backgroundHeight, canvas.getWidth(), + canvas.getHeight(), mTaskbarBackgroundPaint); super.dispatchDraw(canvas); } @@ -132,6 +135,15 @@ public class TaskbarDragLayer extends BaseDragLayer { invalidate(); } + /** + * Sets the translation of the background color behind all the Taskbar contents. + * @param offset 0 is fully onscreen, 1 is fully offscreen. + */ + protected void setTaskbarBackgroundOffset(float offset) { + mTaskbarBackgroundOffset = offset; + invalidate(); + } + @Override public boolean dispatchTouchEvent(MotionEvent ev) { TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java index db5c387770..e15e9ffa71 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java @@ -39,6 +39,8 @@ public class TaskbarDragLayerController { // Alpha properties for taskbar background. private final AnimatedFloat mBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha); private final AnimatedFloat mBgNavbar = new AnimatedFloat(this::updateBackgroundAlpha); + // Translation property for taskbar background. + private final AnimatedFloat mBgOffset = new AnimatedFloat(this::updateBackgroundOffset); // Initialized in init. private TaskbarControllers mControllers; @@ -78,10 +80,18 @@ public class TaskbarDragLayerController { return mBgNavbar; } + public AnimatedFloat getTaskbarBackgroundOffset() { + return mBgOffset; + } + private void updateBackgroundAlpha() { mTaskbarDragLayer.setTaskbarBackgroundAlpha(Math.max(mBgNavbar.value, mBgTaskbar.value)); } + private void updateBackgroundOffset() { + mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value); + } + /** * Callbacks for {@link TaskbarDragLayer} to interact with its controller. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java new file mode 100644 index 0000000000..57600d7356 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -0,0 +1,262 @@ +/* + * 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 android.view.HapticFeedbackConstants.LONG_PRESS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.annotation.Nullable; +import android.content.SharedPreferences; +import android.content.res.Resources; + +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; +import com.android.quickstep.AnimatedFloat; + +/** + * Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to + * create a cohesive animation between stashed/unstashed states. + */ +public class TaskbarStashController { + + /** + * How long to stash/unstash when manually invoked via long press. + */ + private static final long TASKBAR_STASH_DURATION = 300; + + /** + * The scale TaskbarView animates to when being stashed. + */ + private static final float STASHED_TASKBAR_SCALE = 0.5f; + + /** + * The SharedPreferences key for whether user has manually stashed the taskbar. + */ + private static final String SHARED_PREFS_STASHED_KEY = "taskbar_is_stashed"; + + /** + * Whether taskbar should be stashed out of the box. + */ + private static final boolean DEFAULT_STASHED_PREF = false; + + private final TaskbarActivityContext mActivity; + private final SharedPreferences mPrefs; + private final int mStashedHeight; + private final int mUnstashedHeight; + + // Initialized in init. + private TaskbarControllers mControllers; + // Taskbar background properties. + private AnimatedFloat mTaskbarBackgroundOffset; + // TaskbarView icon properties. + private AlphaProperty mIconAlphaForStash; + private AnimatedFloat mIconScaleForStash; + private AnimatedFloat mIconTranslationYForStash; + // Stashed handle properties. + private AnimatedFloat mTaskbarStashedHandleAlpha; + + /** Whether the user has manually invoked taskbar stashing, which we persist. */ + private boolean mIsStashedInApp; + /** Whether we are currently visually stashed (might change based on launcher state). */ + private boolean mIsStashed = false; + + private @Nullable AnimatorSet mAnimator; + + public TaskbarStashController(TaskbarActivityContext activity) { + mActivity = activity; + mPrefs = Utilities.getPrefs(mActivity); + final Resources resources = mActivity.getResources(); + mStashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size); + mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize; + } + + public void init(TaskbarControllers controllers) { + mControllers = controllers; + + TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController; + mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset(); + + TaskbarViewController taskbarViewController = controllers.taskbarViewController; + mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty( + TaskbarViewController.ALPHA_INDEX_STASH); + mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash(); + mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash(); + + StashedHandleViewController stashedHandleController = + controllers.stashedHandleViewController; + mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha(); + + mIsStashedInApp = supportsStashing() + && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF); + } + + /** + * Returns whether the user can manually stash the taskbar based on the current device state. + */ + private boolean supportsStashing() { + return !mActivity.isThreeButtonNav(); + } + + /** + * Returns whether the taskbar is currently visually stashed. + */ + public boolean isStashed() { + return mIsStashed; + } + + /** + * Returns whether the user has manually stashed the taskbar in apps. + */ + public boolean isStashedInApp() { + return mIsStashedInApp; + } + + public int getContentHeight() { + return isStashed() ? mStashedHeight : mUnstashedHeight; + } + + public int getStashedHeight() { + return mStashedHeight; + } + + /** + * Should be called when long pressing the nav region when taskbar is present. + * @return Whether taskbar was stashed and now is unstashed. + */ + public boolean onLongPressToUnstashTaskbar() { + if (!isStashed()) { + // We only listen for long press on the nav region to unstash the taskbar. To stash the + // taskbar, we use an OnLongClickListener on TaskbarView instead. + return false; + } + if (updateAndAnimateIsStashedInApp(false)) { + mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS); + return true; + } + return false; + } + + /** + * Updates whether we should stash the taskbar when in apps, and animates to the changed state. + * @return Whether we started an animation to either be newly stashed or unstashed. + */ + public boolean updateAndAnimateIsStashedInApp(boolean isStashedInApp) { + if (!supportsStashing()) { + return false; + } + if (mIsStashedInApp != isStashedInApp) { + boolean wasStashed = mIsStashedInApp; + mIsStashedInApp = isStashedInApp; + mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_KEY, mIsStashedInApp).apply(); + boolean isStashed = mIsStashedInApp; + if (wasStashed != isStashed) { + createAnimToIsStashed(isStashed, TASKBAR_STASH_DURATION).start(); + return true; + } + } + return false; + } + + /** + * Starts an animation to the new stashed state with a default duration. + */ + public void animateToIsStashed(boolean isStashed) { + animateToIsStashed(isStashed, TASKBAR_STASH_DURATION); + } + + /** + * Starts an animation to the new stashed state with the specified duration. + */ + public void animateToIsStashed(boolean isStashed, long duration) { + createAnimToIsStashed(isStashed, duration).start(); + } + + private Animator createAnimToIsStashed(boolean isStashed, long duration) { + AnimatorSet fullLengthAnimatorSet = new AnimatorSet(); + // Not exactly half and may overlap. See [first|second]HalfDurationScale below. + AnimatorSet firstHalfAnimatorSet = new AnimatorSet(); + AnimatorSet secondHalfAnimatorSet = new AnimatorSet(); + + final float firstHalfDurationScale; + final float secondHalfDurationScale; + + if (isStashed) { + firstHalfDurationScale = 0.75f; + secondHalfDurationScale = 0.5f; + final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f; + + fullLengthAnimatorSet.playTogether( + mTaskbarBackgroundOffset.animateToValue(1), + mIconTranslationYForStash.animateToValue(stashTranslation) + ); + firstHalfAnimatorSet.playTogether( + mIconAlphaForStash.animateToValue(0), + mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE) + ); + secondHalfAnimatorSet.playTogether( + mTaskbarStashedHandleAlpha.animateToValue(1) + ); + } else { + firstHalfDurationScale = 0.5f; + secondHalfDurationScale = 0.75f; + + fullLengthAnimatorSet.playTogether( + mTaskbarBackgroundOffset.animateToValue(0), + mIconScaleForStash.animateToValue(1), + mIconTranslationYForStash.animateToValue(0) + ); + firstHalfAnimatorSet.playTogether( + mTaskbarStashedHandleAlpha.animateToValue(0) + ); + secondHalfAnimatorSet.playTogether( + mIconAlphaForStash.animateToValue(1) + ); + } + + Animator stashedHandleRevealAnim = mControllers.stashedHandleViewController + .createRevealAnimToIsStashed(isStashed); + if (stashedHandleRevealAnim != null) { + fullLengthAnimatorSet.play(stashedHandleRevealAnim); + } + + fullLengthAnimatorSet.setDuration(duration); + firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale)); + secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale)); + secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale))); + + if (mAnimator != null) { + mAnimator.cancel(); + } + mAnimator = new AnimatorSet(); + mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet, + secondHalfAnimatorSet); + mAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mIsStashed = isStashed; + } + + @Override + public void onAnimationEnd(Animator animation) { + mAnimator = null; + } + }); + return mAnimator; + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index 260cedc706..6d0e3c6baf 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -33,4 +33,8 @@ public class TaskbarUIController { } protected void updateContentInsets(Rect outContentInsets) { } + + protected boolean onLongPressToUnstashTaskbar() { + return false; + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 7753f966dc..820d40af27 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -94,8 +94,10 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) { mControllerCallbacks = callbacks; - mIconClickListener = mControllerCallbacks.getOnClickListener(); - mIconLongClickListener = mControllerCallbacks.getOnLongClickListener(); + mIconClickListener = mControllerCallbacks.getIconOnClickListener(); + mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener(); + + setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener()); } private void removeAndRecycle(View view) { @@ -235,6 +237,10 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar return isShown() && mIconLayoutBounds.contains(xInOurCoordinates, yInOurCoorindates); } + public Rect getIconLayoutBounds() { + return mIconLayoutBounds; + } + // FolderIconParent implemented methods. @Override diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index c7ac4a4f5b..50c26b30b2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -17,8 +17,8 @@ package com.android.launcher3.taskbar; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; -import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.quickstep.AnimatedFloat.VALUE; import android.graphics.Rect; import android.view.View; @@ -28,6 +28,7 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.MultiValueAlpha; +import com.android.quickstep.AnimatedFloat; /** * Handles properties/data collection, then passes the results to TaskbarView to render. @@ -38,10 +39,16 @@ public class TaskbarViewController { public static final int ALPHA_INDEX_HOME = 0; public static final int ALPHA_INDEX_IME = 1; public static final int ALPHA_INDEX_KEYGUARD = 2; + public static final int ALPHA_INDEX_STASH = 3; private final TaskbarActivityContext mActivity; private final TaskbarView mTaskbarView; private final MultiValueAlpha mTaskbarIconAlpha; + private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale); + private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat( + this::updateTranslationY); + private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat( + this::updateTranslationY); // Initialized in init. private TaskbarControllers mControllers; @@ -54,7 +61,7 @@ public class TaskbarViewController { public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) { mActivity = activity; mTaskbarView = taskbarView; - mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 3); + mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 4); mTaskbarIconAlpha.setUpdateVisibility(true); } @@ -62,6 +69,8 @@ public class TaskbarViewController { mControllers = controllers; mTaskbarView.init(new TaskbarViewCallbacks()); mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; + + mTaskbarIconScaleForStash.updateValue(1f); } public boolean areIconsVisible() { @@ -86,6 +95,32 @@ public class TaskbarViewController { mTaskbarView.setClickAndLongClickListenersForIcon(icon); } + public Rect getIconLayoutBounds() { + return mTaskbarView.getIconLayoutBounds(); + } + + public AnimatedFloat getTaskbarIconScaleForStash() { + return mTaskbarIconScaleForStash; + } + + public AnimatedFloat getTaskbarIconTranslationYForStash() { + return mTaskbarIconTranslationYForStash; + } + + /** + * Applies scale properties for the entire TaskbarView (rather than individual icons). + */ + private void updateScale() { + float scale = mTaskbarIconScaleForStash.value; + mTaskbarView.setScaleX(scale); + mTaskbarView.setScaleY(scale); + } + + private void updateTranslationY() { + mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value + + mTaskbarIconTranslationYForStash.value); + } + /** * Sets the taskbar icon alignment relative to Launcher hotseat icons * @param alignmentRatio [0, 1] @@ -116,7 +151,7 @@ public class TaskbarViewController { / launcherDp.numShownHotseatIcons; int offsetY = launcherDp.getTaskbarOffsetY(); - setter.setFloat(mTaskbarView, VIEW_TRANSLATE_Y, -offsetY, LINEAR); + setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR); int collapsedHeight = mActivity.getDeviceProfile().taskbarSize; int expandedHeight = collapsedHeight + offsetY; @@ -144,12 +179,16 @@ public class TaskbarViewController { * Callbacks for {@link TaskbarView} to interact with its controller. */ public class TaskbarViewCallbacks { - public View.OnClickListener getOnClickListener() { + public View.OnClickListener getIconOnClickListener() { return mActivity::onTaskbarIconClicked; } - public View.OnLongClickListener getOnLongClickListener() { + public View.OnLongClickListener getIconOnLongClickListener() { return mControllers.taskbarDragController::startDragOnLongClick; } + + public View.OnLongClickListener getBackgroundOnLongClickListener() { + return view -> mControllers.taskbarStashController.updateAndAnimateIsStashedInApp(true); + } } } diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java index f7e8781573..95c871099b 100644 --- a/quickstep/src/com/android/quickstep/AnimatedFloat.java +++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java @@ -53,6 +53,16 @@ public class AnimatedFloat { mUpdateCallback = updateCallback; } + /** + * Returns an animation from the current value to the given value. + */ + public ObjectAnimator animateToValue(float end) { + return animateToValue(value, end); + } + + /** + * Returns an animation from the given start value to the given end value. + */ public ObjectAnimator animateToValue(float start, float end) { cancelAnimation(); mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, start, end); diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 2699b0795b..1412b1add6 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -363,6 +363,14 @@ public abstract class BaseActivityInterface TYPE_NO_OP; diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java index 799a4c2516..09474a1ac2 100644 --- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java @@ -302,6 +302,15 @@ public final class LauncherActivityInterface extends } } + @Override + public boolean onLongPressToUnstashTaskbar() { + LauncherTaskbarUIController taskbarController = getTaskbarController(); + if (taskbarController == null) { + return super.onLongPressToUnstashTaskbar(); + } + return taskbarController.onLongPressToUnstashTaskbar(); + } + @Override protected int getOverviewScrimColorForState(BaseQuickstepLauncher launcher, LauncherState state) { diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index 0ebe13be8d..9e69ef9b52 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -46,6 +46,8 @@ public class RecentsAnimationController { private boolean mUseLauncherSysBarFlags = false; private boolean mSplitScreenMinimized = false; private boolean mFinishRequested = false; + // Only valid when mFinishRequested == true. + private boolean mFinishTargetIsLauncher; private RunnableList mPendingFinishCallbacks = new RunnableList(); public RecentsAnimationController(RecentsAnimationControllerCompat controller, @@ -145,6 +147,7 @@ public class RecentsAnimationController { // Finish not yet requested mFinishRequested = true; + mFinishTargetIsLauncher = toRecents; mOnFinishedListener.accept(this); mPendingFinishCallbacks.add(callback); UI_HELPER_EXECUTOR.execute(() -> { @@ -201,4 +204,12 @@ public class RecentsAnimationController { public RecentsAnimationControllerCompat getController() { return mController; } + + /** + * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether + * the animation was finished to launcher vs an app. + */ + public boolean getFinishTargetIsLauncher() { + return mFinishTargetIsLauncher; + } } diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 47ca3d2024..5d701d4e14 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -95,6 +95,7 @@ import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer; import com.android.quickstep.inputconsumers.ResetGestureInputConsumer; import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer; import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer; +import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.util.ProtoTracer; @@ -673,6 +674,14 @@ public class TouchInteractionService extends Service implements PluginListener