Snap for 7479185 from a29220f6b6fd7a87f8550e479b216c693642b854 to sc-v2-release
Change-Id: I3ec2119bbf229f5dd9a798801f14054f13abd76c
diff --git a/quickstep/res/drawable/ic_all_set.xml b/quickstep/res/drawable/ic_all_set.xml
index a6852aa..656c596 100644
--- a/quickstep/res/drawable/ic_all_set.xml
+++ b/quickstep/res/drawable/ic_all_set.xml
@@ -15,10 +15,10 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="42dp"
- android:height="40dp"
+ android:height="42dp"
android:viewportWidth="42"
- android:viewportHeight="40">
+ android:viewportHeight="42">
<path
- android:pathData="M38,14H25.38L27.28,4.86L27.34,4.22C27.34,3.4 27,2.64 26.46,2.1L24.34,0C24.34,0 10.16,13.7 10,14H0V40H32C33.66,40 35.08,39 35.68,37.56L41.72,23.46C41.9,23 42,22.52 42,22V18C42,15.8 40.2,14 38,14ZM10,36H4V18H10V36ZM38,22L32,36H14V16L22.68,7.32L20,18H38V22Z"
+ android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"
android:fillColor="#FFFFFF"/>
</vector>
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/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 66ccee6..2d1e304 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -497,8 +497,6 @@
view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets()));
viewsToAnimate.add(mLauncher.getHotseat());
- // Add QSB
- viewsToAnimate.add(mLauncher.findViewById(R.id.search_container_all_apps));
viewsToAnimate.forEach(view -> {
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 12ac0f5..06fd660 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..37cb979 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()) {
@@ -832,7 +841,13 @@
}
private void reset() {
- mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
+ if (mResetGestureInputConsumer != null) {
+ mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
+ } else {
+ // mResetGestureInputConsumer isn't initialized until onUserUnlocked(), so reset to
+ // NO_OP until then (we never want these to be null).
+ mConsumer = mUncheckedConsumer = InputConsumer.NO_OP;
+ }
mGestureState = DEFAULT_STATE;
// By default, use batching of the input events, but check receiver before using in the rare
// case that the monitor was disposed before the swipe settled
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);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
index 50da93b..df94d0b 100644
--- a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
+++ b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.states.StateAnimationConfig.SKIP_SCRIM;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.view.View;
@@ -98,6 +99,14 @@
alpha.setDuration(DURATION_MS);
alpha.setInterpolator(Interpolators.DECELERATED_EASE);
mAnimators.play(alpha);
+
+ mAnimators.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ SCALE_PROPERTY.set(v, 1f);
+ v.setAlpha(1f);
+ }
+ });
}
/**
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 45bcc87..4332c76 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -2236,7 +2236,7 @@
public PendingAnimation createTaskDismissAnimation(TaskView dismissedTaskView,
boolean animateTaskView, boolean shouldRemoveTask, long duration) {
if (mPendingAnimation != null) {
- mPendingAnimation.createPlaybackController().dispatchOnCancel();
+ mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
}
PendingAnimation anim = new PendingAnimation(duration);
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index 0bfa2b2..3eaa7ea 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -20,7 +20,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
- android:layout_marginBottom="@dimen/widget_list_entry_bottom_margin"
android:paddingVertical="@dimen/widget_list_header_view_vertical_padding"
android:orientation="horizontal"
launcher:appIconSize="48dp">
diff --git a/res/layout/widgets_table_container.xml b/res/layout/widgets_table_container.xml
index 4f70a05..fac21fd 100644
--- a/res/layout/widgets_table_container.xml
+++ b/res/layout/widgets_table_container.xml
@@ -18,5 +18,4 @@
android:id="@+id/widgets_table"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginHorizontal="16dp"
- android:layout_marginBottom="@dimen/widget_list_entry_bottom_margin"/>
+ android:layout_marginHorizontal="16dp"/>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f4a0a3d..f5c08c5 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -147,7 +147,7 @@
<dimen name="widget_list_content_corner_radius">4dp</dimen>
<dimen name="widget_list_header_view_vertical_padding">20dp</dimen>
- <dimen name="widget_list_entry_bottom_margin">2dp</dimen>
+ <dimen name="widget_list_entry_spacing">2dp</dimen>
<dimen name="widget_preview_shadow_blur">0.5dp</dimen>
<dimen name="widget_preview_key_shadow_distance">1dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index d941716..0e2dff0 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -16,6 +16,7 @@
-->
<resources>
<item type="id" name="apps_list_view_work" />
+ <item type="id" name="tag_widget_entry" />
<item type="id" name="view_type_widgets_list" />
<item type="id" name="view_type_widgets_header" />
<item type="id" name="view_type_widgets_search_header" />
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 7d8b82a..85ca280 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -278,12 +278,19 @@
}
}
- public void dispatchOnStart() {
+ public AnimatorPlaybackController dispatchOnStart() {
callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationStart);
+ return this;
}
- public void dispatchOnCancel() {
+ public AnimatorPlaybackController dispatchOnCancel() {
callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationCancel);
+ return this;
+ }
+
+ public AnimatorPlaybackController dispatchOnEnd() {
+ callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationEnd);
+ return this;
}
public void dispatchSetInterpolator(TimeInterpolator interpolator) {
@@ -328,7 +335,7 @@
public void onAnimationSuccess(Animator animator) {
// We wait for the spring (if any) to finish running before completing the end callback.
if (!mDispatched) {
- callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationEnd);
+ dispatchOnEnd();
if (mEndAction != null) {
mEndAction.run();
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 3abcc2b..7091d2b 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -168,6 +168,8 @@
cellY = info.cellY;
spanX = info.spanX;
spanY = info.spanY;
+ minSpanX = info.minSpanX;
+ minSpanY = info.minSpanY;
rank = info.rank;
screenId = info.screenId;
itemType = info.itemType;
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index dbd9c28..915e140 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -113,12 +113,6 @@
Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this);
}
- @Override
- protected void onStop() {
- super.onStop();
- finish();
- }
-
/**
* Obtains the preference fragment to instantiate in this activity.
*
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 13d6568..b34af97 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -209,7 +209,7 @@
// Cancel the current animation. This will reset mState to mCurrentStableState, so store it.
STATE_TYPE fromState = mState;
- mConfig.reset();
+ cancelAnimation();
if (!animated) {
mAtomicAnimationFactory.cancelAllStateElementAnimation();
@@ -303,7 +303,7 @@
public AnimatorPlaybackController createAnimationToNewWorkspace(STATE_TYPE state,
StateAnimationConfig config) {
config.userControlled = true;
- mConfig.reset();
+ cancelAnimation();
config.copyTo(mConfig);
mConfig.playbackController = createAnimationToNewWorkspaceInternal(state)
.createPlaybackController();
@@ -393,6 +393,11 @@
*/
public void cancelAnimation() {
mConfig.reset();
+ // It could happen that a new animation is set as a result of an endListener on the
+ // existing animation.
+ while (mConfig.currentAnimation != null || mConfig.playbackController != null) {
+ mConfig.reset();
+ }
}
public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) {
@@ -508,14 +513,19 @@
* Cancels the current animation and resets config variables.
*/
public void reset() {
+ AnimatorSet anim = currentAnimation;
+ AnimatorPlaybackController pc = playbackController;
+
DEFAULT.copyTo(this);
targetState = null;
+ currentAnimation = null;
+ playbackController = null;
+ changeId++;
- if (playbackController != null) {
- playbackController.getAnimationPlayer().cancel();
- playbackController.dispatchOnCancel();
- } else if (currentAnimation != null) {
- AnimatorSet anim = currentAnimation;
+ if (pc != null) {
+ pc.getAnimationPlayer().cancel();
+ pc.dispatchOnCancel().dispatchOnEnd();
+ } else if (anim != null) {
anim.setDuration(0);
if (!anim.isStarted()) {
// If the animation is not started the listeners do not get notified,
@@ -525,10 +535,6 @@
}
anim.cancel();
}
-
- currentAnimation = null;
- playbackController = null;
- changeId++;
}
@Override
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index 5be9529..8591872 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -16,6 +16,8 @@
package com.android.launcher3.util;
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
import android.util.FloatProperty;
import android.view.View;
@@ -121,5 +123,12 @@
public String toString() {
return Float.toString(mValue);
}
+
+ /**
+ * Creates and returns an Animator from the current value to the given value.
+ */
+ public Animator animateToValue(float value) {
+ return ObjectAnimator.ofFloat(this, VALUE, value);
+ }
}
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java
index c0f89bc..055e4ec 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListSearchHeaderEntry.java
@@ -60,7 +60,7 @@
WidgetsListSearchHeaderEntry otherEntry = (WidgetsListSearchHeaderEntry) obj;
return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
&& mTitleSectionName.equals(otherEntry.mTitleSectionName)
- && mIsWidgetListShown;
+ && mIsWidgetListShown == otherEntry.mIsWidgetListShown;
}
/** Returns a copy of this {@link WidgetsListSearchHeaderEntry} with the widget list shown. */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 5d9adf0..b668c90 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_APP_EXPANDED;
import android.content.Context;
+import android.graphics.Rect;
import android.os.Process;
import android.util.Log;
import android.util.Size;
@@ -34,6 +35,7 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.Adapter;
+import androidx.recyclerview.widget.RecyclerView.LayoutParams;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.launcher3.BaseActivity;
@@ -107,7 +109,8 @@
@Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
@Nullable private RecyclerView mRecyclerView;
@Nullable private PackageUserKey mPendingClickHeader;
- private int mShortcutPreviewPadding;
+ private final int mShortcutPreviewPadding;
+ private final int mSpacingBetweenEntries;
private final WidgetPreviewLoadedCallback mPreviewLoadedCallback =
ignored -> updateVisibleEntries();
@@ -141,11 +144,28 @@
mShortcutPreviewPadding =
2 * context.getResources()
.getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
+ mSpacingBetweenEntries =
+ context.getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
}
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
mRecyclerView = recyclerView;
+
+ mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
+ @Override
+ public void getItemOffsets(
+ @NonNull Rect outRect,
+ @NonNull View view,
+ @NonNull RecyclerView parent,
+ @NonNull RecyclerView.State state) {
+ super.getItemOffsets(outRect, view, parent, state);
+ int position = ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
+ boolean isHeader =
+ view.getTag(R.id.tag_widget_entry) instanceof WidgetsListBaseEntry.Header;
+ outRect.top += position > 0 && isHeader ? mSpacingBetweenEntries : 0;
+ }
+ });
}
@Override
@@ -329,7 +349,9 @@
@Override
public void onBindViewHolder(ViewHolder holder, int pos) {
ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
+ WidgetsListBaseEntry entry = mVisibleEntries.get(pos);
viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), pos);
+ holder.itemView.setTag(R.id.tag_widget_entry, entry);
}
@Override
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index cdab964..ef2adbb 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -30,7 +30,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
@@ -59,7 +58,6 @@
@Nullable private HandlerRunnable mIconLoadRequest;
@Nullable private Drawable mIconDrawable;
private final int mIconSize;
- private final int mBottomMarginSize;
private ImageView mAppIcon;
private TextView mTitle;
@@ -86,8 +84,6 @@
R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0);
mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize,
grid.iconSizePx);
- mBottomMarginSize =
- getResources().getDimensionPixelSize(R.dimen.widget_list_entry_bottom_margin);
}
@Override
@@ -146,13 +142,6 @@
public void setExpanded(boolean isExpanded) {
this.mIsExpanded = isExpanded;
mExpandToggle.setChecked(isExpanded);
- if (getLayoutParams() instanceof RecyclerView.LayoutParams) {
- int bottomMargin = isExpanded ? 0 : mBottomMarginSize;
- RecyclerView.LayoutParams layoutParams =
- ((RecyclerView.LayoutParams) getLayoutParams());
- layoutParams.bottomMargin = bottomMargin;
- setLayoutParams(layoutParams);
- }
}
/** Sets the {@link WidgetsListDrawableState} and refreshes the background drawable. */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 3ee8b33..7671841 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -52,7 +52,7 @@
private HeaderViewDimensionsProvider mHeaderViewDimensionsProvider;
private int mLastVisibleWidgetContentTableHeight = 0;
private int mWidgetHeaderHeight = 0;
- private final int mCollapsedHeaderBottomMarginSize;
+ private final int mSpacingBetweenEntries;
@Nullable private OnContentChangeListener mOnContentChangeListener;
public WidgetsRecyclerView(Context context) {
@@ -72,9 +72,9 @@
ActivityContext activity = ActivityContext.lookupContext(getContext());
DeviceProfile grid = activity.getDeviceProfile();
- // The bottom margin used when the header is not expanded.
- mCollapsedHeaderBottomMarginSize =
- getResources().getDimensionPixelSize(R.dimen.widget_list_entry_bottom_margin);
+ // The spacing used between entries.
+ mSpacingBetweenEntries =
+ getResources().getDimensionPixelSize(R.dimen.widget_list_entry_spacing);
}
@Override
@@ -270,16 +270,16 @@
if (untilIndex > mAdapter.getItems().size()) {
untilIndex = mAdapter.getItems().size();
}
- int expandedHeaderPosition = mAdapter.getSelectedHeaderPosition().orElse(-1);
int totalItemsHeight = 0;
for (int i = 0; i < untilIndex; i++) {
WidgetsListBaseEntry entry = mAdapter.getItems().get(i);
if (entry instanceof WidgetsListHeaderEntry
|| entry instanceof WidgetsListSearchHeaderEntry) {
totalItemsHeight += mWidgetHeaderHeight;
- if (expandedHeaderPosition != i) {
- // If the header is collapsed, include the bottom margin it will use.
- totalItemsHeight += mCollapsedHeaderBottomMarginSize;
+ if (i > 0) {
+ // Each header contains the spacing between entries as top decoration, except
+ // the first one.
+ totalItemsHeight += mSpacingBetweenEntries;
}
} else if (entry instanceof WidgetsListContentEntry) {
totalItemsHeight += mLastVisibleWidgetContentTableHeight;