Initial commit of taskbar stashing
- Added StashedHandleViewController to provide properties such as ViewOutlineProvider to animate the handle that's shown in place of taskbar while it's stashed
- Added TaskbarStashController to coordinate the stashed state, including orchestrating the animation across taskbar controllers
- Added TaskbarStashInput consumer to detect long press in the nav region when taskbar is stashed
Behavior:
- Long pressing taskbar background animates to the stashed state by morphing the TaskbarView into the stashed handle view and offsetting the background offscreen
- We persist the stashed state across app launches and reboot; to unstash, long press the stashed handle
- We also visually unstash when going back home
Test: long press tasbkar background when in an app to stash it, long press the resulting stashed handle to unstash; while stashed, swipe up to home to also unstash until launching another app
Bug: 189503603
Change-Id: I698eff785388dff1ef717c76879719d6af236c2d
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index d61a895..dfa17d6 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -15,6 +15,7 @@
-->
<com.android.launcher3.taskbar.TaskbarDragLayer
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/taskbar_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -39,6 +40,7 @@
android:id="@+id/start_nav_buttons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:orientation="horizontal"
android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
android:gravity="center_vertical"
@@ -54,4 +56,14 @@
android:layout_gravity="end"/>
</FrameLayout>
+ <View
+ android:id="@+id/stashed_handle"
+ tools:comment1="The actual size and shape will be set as a ViewOutlineProvider at runtime"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ tools:comment2="TODO: Tint dynamically"
+ android:background="?android:attr/textColorPrimary"
+ android:clipToOutline="true"
+ android:layout_gravity="bottom"/>
+
</com.android.launcher3.taskbar.TaskbarDragLayer>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index d8899a6..4f62b34 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -153,4 +153,7 @@
<dimen name="taskbar_folder_margin">16dp</dimen>
<dimen name="taskbar_nav_buttons_spacing">16dp</dimen>
<dimen name="taskbar_nav_buttons_size">48dp</dimen>
+ <dimen name="taskbar_stashed_size">24dp</dimen>
+ <dimen name="taskbar_stashed_handle_width">220dp</dimen>
+ <dimen name="taskbar_stashed_handle_height">6dp</dimen>
</resources>
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 7d0afe1..c98bc87 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -50,21 +50,23 @@
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 @@
@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 @@
@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 @@
}
}
+ 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 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 @@
}
}
+ @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 0000000..8c14ff6
--- /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 4142610..f4703d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -115,6 +115,7 @@
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 @@
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 c48c28b..8279a47 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -32,6 +32,8 @@
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 @@
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 @@
this.taskbarDragLayerController = taskbarDragLayerController;
this.taskbarViewController = taskbarViewController;
this.taskbarKeyguardController = taskbarKeyguardController;
+ this.stashedHandleViewController = stashedHandleViewController;
+ this.taskbarStashController = taskbarStashController;
}
/**
@@ -67,6 +73,8 @@
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 ac121ab..cd1baf7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -40,10 +40,11 @@
public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
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 @@
@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 @@
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 db5c387..e15e9ff 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -39,6 +39,8 @@
// 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 @@
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 0000000..57600d7
--- /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 260cedc..6d0e3c6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -33,4 +33,8 @@
}
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 7753f96..820d40a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -94,8 +94,10 @@
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 @@
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 c7ac4a4..50c26b3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -17,8 +17,8 @@
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.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 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 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 @@
mControllers = controllers;
mTaskbarView.init(new TaskbarViewCallbacks());
mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+
+ mTaskbarIconScaleForStash.updateValue(1f);
}
public boolean areIconsVisible() {
@@ -86,6 +95,32 @@
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 @@
/ 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 @@
* 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 f7e8781..95c8710 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -53,6 +53,16 @@
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 2699b07..1412b1a 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -364,6 +364,14 @@
}
/**
+ * Called when we detect a long press in the nav region before passing the gesture slop.
+ * @return Whether taskbar handled the long press, and thus should cancel the gesture.
+ */
+ public boolean onLongPressToUnstashTaskbar() {
+ return false;
+ }
+
+ /**
* Returns the color of the scrim behind overview when at rest in this state.
* Return {@link Color#TRANSPARENT} for no scrim.
*/
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 0b2a057..3580ee5 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -39,6 +39,7 @@
int TYPE_OVERSCROLL = 1 << 9;
int TYPE_SYSUI_OVERLAY = 1 << 10;
int TYPE_ONE_HANDED = 1 << 11;
+ int TYPE_TASKBAR_STASH = 1 << 12;
String[] NAMES = new String[] {
"TYPE_NO_OP", // 0
@@ -53,6 +54,7 @@
"TYPE_OVERSCROLL", // 9
"TYPE_SYSUI_OVERLAY", // 10
"TYPE_ONE_HANDED", // 11
+ "TYPE_TASKBAR_STASH", // 12
};
InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 799a4c2..09474a1 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -303,6 +303,15 @@
}
@Override
+ public boolean onLongPressToUnstashTaskbar() {
+ LauncherTaskbarUIController taskbarController = getTaskbarController();
+ if (taskbarController == null) {
+ return super.onLongPressToUnstashTaskbar();
+ }
+ return taskbarController.onLongPressToUnstashTaskbar();
+ }
+
+ @Override
protected int getOverviewScrimColorForState(BaseQuickstepLauncher launcher,
LauncherState state) {
return state.getWorkspaceScrimColor(launcher);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 0ebe13b..9e69ef9 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -46,6 +46,8 @@
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 @@
// Finish not yet requested
mFinishRequested = true;
+ mFinishTargetIsLauncher = toRecents;
mOnFinishedListener.accept(this);
mPendingFinishCallbacks.add(callback);
UI_HELPER_EXECUTOR.execute(() -> {
@@ -201,4 +204,12 @@
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 47ca3d2..5d701d4 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.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 @@
mDeviceState, event);
}
+ // If Taskbar is present, we listen for long press to unstash it.
+ BaseActivityInterface activityInterface = newGestureState.getActivityInterface();
+ StatefulActivity activity = activityInterface.getCreatedActivity();
+ if (activity != null && activity.getDeviceProfile().isTaskbarPresent) {
+ base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat,
+ activityInterface);
+ }
+
if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
OverscrollPlugin plugin = null;
if (FeatureFlags.FORCE_LOCAL_OVERSCROLL_PLUGIN.get()) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
new file mode 100644
index 0000000..83f689f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
@@ -0,0 +1,66 @@
+/*
+ * 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.quickstep.inputconsumers;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.InputConsumer;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Listens for a long press, and cancels the current gesture if that causes Taskbar to be unstashed.
+ */
+public class TaskbarStashInputConsumer extends DelegateInputConsumer {
+
+ private final BaseActivityInterface mActivityInterface;
+ private final GestureDetector mLongPressDetector;
+
+ public TaskbarStashInputConsumer(Context context, InputConsumer delegate,
+ InputMonitorCompat inputMonitor, BaseActivityInterface activityInterface) {
+ super(delegate, inputMonitor);
+ mActivityInterface = activityInterface;
+
+ mLongPressDetector = new GestureDetector(context, new SimpleOnGestureListener() {
+ @Override
+ public void onLongPress(MotionEvent motionEvent) {
+ onLongPressDetected(motionEvent);
+ }
+ });
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_TASKBAR_STASH | mDelegate.getType();
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ mLongPressDetector.onTouchEvent(ev);
+ if (mState != STATE_ACTIVE) {
+ mDelegate.onMotionEvent(ev);
+ }
+ }
+
+ private void onLongPressDetected(MotionEvent motionEvent) {
+ if (mActivityInterface.onLongPressToUnstashTaskbar()) {
+ setActive(motionEvent);
+ }
+ }
+}