From 363a841a0408e0f4a69ec7882448790a01602c1c Mon Sep 17 00:00:00 2001 From: Ats Jenk Date: Mon, 22 Apr 2024 18:03:54 -0700 Subject: [PATCH] Drag bubble in bubble bar to other side Adds support to pin bubble bar to other side of the screen by dragging from one bubble. The entire bubble bar will move to the other side. Implements the BaseBubbleBarPinController to handle dragging an individual BubbleView. Updates how translation and alpha get applied during BubbleBarView location animation when a bubble is being dragged. Dragged bubble is part of the BubbleBarView and when it is being dragged, and we animate the bubble bar to the other side, we need to ensure that the animation does not affect the dragged bubble. Updating the translation and alpha for the BubbleBarView ViewGroup will affect the dragged bubble as well. Updating how translation and alpha gets applied to BubbleBarView so that it gets applied to individual child views and then we can skip applying it to the dragged view. Bug: 330585402 Flag: ACONFIG com.android.wm.shell.enable_bubble_bar DEVELOPMENT Test: drag bubble from expanded bubble bar to left and right Change-Id: I1fe2ba9fd466ff97d3b3af763bdcce30c3f98606 --- .../bg_bubble_expanded_view_drop_target.xml | 24 ++ .../bubble_expanded_view_drop_target.xml | 23 ++ quickstep/res/values/dimens.xml | 3 + .../taskbar/TaskbarActivityContext.java | 3 + .../taskbar/bubbles/BubbleBarBackground.kt | 18 +- .../taskbar/bubbles/BubbleBarView.java | 260 +++++++++++++++--- .../bubbles/BubbleBarViewController.java | 37 ++- .../taskbar/bubbles/BubbleControllers.java | 6 +- .../taskbar/bubbles/BubbleDragController.java | 69 ++++- .../taskbar/bubbles/BubblePinController.kt | 97 +++++++ .../launcher3/taskbar/bubbles/BubbleView.java | 32 +++ .../com/android/quickstep/SystemUiProxy.java | 24 +- 12 files changed, 517 insertions(+), 79 deletions(-) create mode 100644 quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml create mode 100644 quickstep/res/layout/bubble_expanded_view_drop_target.xml create mode 100644 quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt diff --git a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml new file mode 100644 index 0000000000..98aab67987 --- /dev/null +++ b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/quickstep/res/layout/bubble_expanded_view_drop_target.xml b/quickstep/res/layout/bubble_expanded_view_drop_target.xml new file mode 100644 index 0000000000..15ec49a6b0 --- /dev/null +++ b/quickstep/res/layout/bubble_expanded_view_drop_target.xml @@ -0,0 +1,23 @@ + + + + \ No newline at end of file diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index b862d7c07d..c5f25ad87e 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -456,6 +456,9 @@ 36dp + 16dp + 412dp + 16dp diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index ff76e21faf..708eee6684 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -106,6 +106,7 @@ import com.android.launcher3.taskbar.bubbles.BubbleBarViewController; import com.android.launcher3.taskbar.bubbles.BubbleControllers; import com.android.launcher3.taskbar.bubbles.BubbleDismissController; import com.android.launcher3.taskbar.bubbles.BubbleDragController; +import com.android.launcher3.taskbar.bubbles.BubblePinController; import com.android.launcher3.taskbar.bubbles.BubbleStashController; import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController; import com.android.launcher3.taskbar.navbutton.NearestTouchFrame; @@ -259,6 +260,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { new BubbleDragController(this), new BubbleDismissController(this, mDragLayer), new BubbleBarPinController(this, mDragLayer, + () -> getDeviceProfile().getDisplayInfo().currentSize), + new BubblePinController(this, mDragLayer, () -> getDeviceProfile().getDisplayInfo().currentSize) )); } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt index ec47c4f514..90c3ea726e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt @@ -131,21 +131,15 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float) // Draw background. val radius = backgroundHeight / 2f - val left = if (anchorLeft) 0f else bounds.width().toFloat() - width - val right = if (anchorLeft) width else bounds.width().toFloat() - canvas.drawRoundRect( - left, - pointerVisibleHeight, - right, - bounds.height().toFloat(), - radius, - radius, - paint - ) + val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - width) + val right = bounds.left + (if (anchorLeft) width else bounds.width().toFloat()) + val top = bounds.top + pointerVisibleHeight + val bottom = bounds.top + bounds.height().toFloat() + canvas.drawRoundRect(left, top, right, bottom, radius, radius, paint) if (showingArrow) { // Draw arrow. - val transX = arrowPositionX - pointerWidth / 2f + val transX = bounds.left + arrowPositionX - pointerWidth / 2f canvas.translate(transX, 0f) arrowDrawable.draw(canvas) } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java index 60e8abe9ac..de93ba5859 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java @@ -16,6 +16,7 @@ package com.android.launcher3.taskbar.bubbles; import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE; +import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import android.animation.Animator; @@ -29,6 +30,7 @@ import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.FloatProperty; import android.util.LayoutDirection; import android.util.Log; import android.view.Gravity; @@ -79,6 +81,7 @@ public class BubbleBarView extends FrameLayout { // TODO: (b/273594744) calculate the amount of space we have and base the max on that // if it's smaller than 5. private static final int MAX_BUBBLES = 5; + private static final int MAX_VISIBLE_BUBBLES_COLLAPSED = 2; private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200; private static final int WIDTH_ANIMATION_DURATION_MS = 200; @@ -94,6 +97,40 @@ public class BubbleBarView extends FrameLayout { // During fade in animation we shift the bubble bar 1/60th of the screen width private static final float FADE_IN_ANIM_POSITION_SHIFT = 1 / 60f; + /** + * Custom property to set translationX value for the bar view while a bubble is being dragged. + * Skips applying translation to the dragged bubble. + */ + private static final FloatProperty BUBBLE_DRAG_TRANSLATION_X = + new FloatProperty<>("bubbleDragTranslationX") { + @Override + public void setValue(BubbleBarView bubbleBarView, float translationX) { + bubbleBarView.setTranslationXDuringBubbleDrag(translationX); + } + + @Override + public Float get(BubbleBarView bubbleBarView) { + return bubbleBarView.mTranslationXDuringDrag; + } + }; + + /** + * Custom property to set alpha value for the bar view while a bubble is being dragged. + * Skips applying alpha to the dragged bubble. + */ + private static final FloatProperty BUBBLE_DRAG_ALPHA = + new FloatProperty<>("bubbleDragAlpha") { + @Override + public void setValue(BubbleBarView bubbleBarView, float alpha) { + bubbleBarView.setAlphaDuringBubbleDrag(alpha); + } + + @Override + public Float get(BubbleBarView bubbleBarView) { + return bubbleBarView.mAlphaDuringDrag; + } + }; + private final BubbleBarBackground mBubbleBarBackground; private boolean mIsAnimatingNewBubble = false; @@ -117,6 +154,8 @@ public class BubbleBarView extends FrameLayout { private final float mDragElevation; private final int mPointerSize; + private final Rect mTempBackgroundBounds = new Rect(); + // Whether the bar is expanded (i.e. the bubble activity is being displayed). private boolean mIsBarExpanded = false; // The currently selected bubble view. @@ -150,6 +189,8 @@ public class BubbleBarView extends FrameLayout { @Nullable private BubbleView mDraggedBubbleView; + private float mTranslationXDuringDrag = 0f; + private float mAlphaDuringDrag = 0f; private int mPreviousLayoutDirection = LayoutDirection.UNDEFINED; @@ -259,8 +300,10 @@ public class BubbleBarView extends FrameLayout { setPivotX(mRelativePivotX * getWidth()); setPivotY(mRelativePivotY * getHeight()); - // Position the views - updateChildrenRenderNodeProperties(mBubbleBarLocation); + if (!mDragging) { + // Position the views when not dragging + updateChildrenRenderNodeProperties(mBubbleBarLocation); + } } @Override @@ -302,17 +345,15 @@ public class BubbleBarView extends FrameLayout { mBubbleBarLocationAnimator.cancel(); mBubbleBarLocationAnimator = null; } - setTranslationX(0f); - setAlpha(1f); + resetDragAnimation(); if (bubbleBarLocation != mBubbleBarLocation) { mBubbleBarLocation = bubbleBarLocation; onBubbleBarLocationChanged(); - invalidate(); } } /** - * Set whether this view is being currently being dragged + * Set whether this view is currently being dragged */ public void setIsDragging(boolean dragging) { if (mDragging == dragging) { @@ -326,7 +367,7 @@ public class BubbleBarView extends FrameLayout { * Get translation for bubble bar when drag is released and it needs to animate back to the * resting position. * Resting position is based on the supplied location. If the supplied location is different - * from the internal location that was used to lay out the bubble bar, translation values are + * from the internal location that was used during bubble bar layout, translation values are * calculated to position the bar at the desired location. * * @param initialTranslation initial bubble bar translation at the start of drag @@ -353,6 +394,30 @@ public class BubbleBarView extends FrameLayout { return dragEndTranslation; } + /** + * Get translation for a bubble when drag is released and it needs to animate back to the + * resting position. + * Resting position is based on the supplied location. If the supplied location is different + * from the internal location that was used during bubble bar layout, translation values are + * calculated to position the bar at the desired location. + * + * @param initialTranslation initial bubble bar translation at the start of drag + * @param location desired location of the bubble bar when drag is released + * @return point with x and y values representing translation on x and y-axis + */ + public PointF getDraggedBubbleReleaseTranslation(PointF initialTranslation, + BubbleBarLocation location) { + // Start with bubble bar translation + final PointF dragEndTranslation = new PointF( + getBubbleBarDragReleaseTranslation(initialTranslation, location)); + // Apply individual bubble translation, as the order may have changed + int viewIndex = indexOfChild(mDraggedBubbleView); + dragEndTranslation.x += getExpandedBubbleTranslationX(viewIndex, + getChildCount(), + location.isOnLeft(isLayoutRtl())); + return dragEndTranslation; + } + private float getDistanceFromOtherSide() { // Calculate the shift needed to position the bubble bar on the other side int displayWidth = getResources().getDisplayMetrics().widthPixels; @@ -393,17 +458,18 @@ public class BubbleBarView extends FrameLayout { mBubbleBarLocationAnimator.start(); } - private Animator getLocationUpdateFadeOutAnimator(BubbleBarLocation bubbleBarLocation) { + private Animator getLocationUpdateFadeOutAnimator(BubbleBarLocation newLocation) { + final FloatProperty txProp = getLocationAnimTranslationXProperty(); final float shift = getResources().getDisplayMetrics().widthPixels * FADE_OUT_ANIM_POSITION_SHIFT; - final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl()); - final float tx = getTranslationX() + (onLeft ? shift : -shift); + final boolean onLeft = newLocation.isOnLeft(isLayoutRtl()); + final float tx = txProp.get(this) + (onLeft ? -shift : shift); - ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, TRANSLATION_X, tx) - .setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS); + ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, txProp, tx).setDuration( + FADE_OUT_ANIM_POSITION_DURATION_MS); positionAnim.setInterpolator(EMPHASIZED_ACCELERATE); - ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, ALPHA, 0f) + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 0f) .setDuration(FADE_OUT_ANIM_ALPHA_DURATION_MS); alphaAnim.setStartDelay(FADE_OUT_ANIM_ALPHA_DELAY_MS); @@ -412,14 +478,14 @@ public class BubbleBarView extends FrameLayout { return animatorSet; } - private Animator getLocationUpdateFadeInAnimator(BubbleBarLocation animatedLocation) { + private Animator getLocationUpdateFadeInAnimator(BubbleBarLocation newLocation) { final float shift = getResources().getDisplayMetrics().widthPixels * FADE_IN_ANIM_POSITION_SHIFT; - final boolean onLeft = animatedLocation.isOnLeft(isLayoutRtl()); + final boolean onLeft = newLocation.isOnLeft(isLayoutRtl()); final float startTx; final float finalTx; - if (animatedLocation == mBubbleBarLocation) { + if (newLocation == mBubbleBarLocation) { // Animated location matches layout location. finalTx = 0; } else { @@ -439,9 +505,9 @@ public class BubbleBarView extends FrameLayout { .setEndValue(finalTx) .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) .setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS) - .build(this, VIEW_TRANSLATE_X); + .build(this, getLocationAnimTranslationXProperty()); - ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, ALPHA, 1f) + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 1f) .setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS); AnimatorSet animatorSet = new AnimatorSet(); @@ -449,6 +515,84 @@ public class BubbleBarView extends FrameLayout { return animatorSet; } + /** + * Get property that can be used to animate the translation-x value for the bar. + * When a bubble is being dragged, uses {@link #BUBBLE_DRAG_TRANSLATION_X}. + * Falls back to {@link com.android.launcher3.LauncherAnimUtils#VIEW_TRANSLATE_X} otherwise. + */ + private FloatProperty getLocationAnimTranslationXProperty() { + return mDraggedBubbleView == null ? VIEW_TRANSLATE_X : BUBBLE_DRAG_TRANSLATION_X; + } + + /** + * Get property that can be used to animate the alpha value for the bar. + * When a bubble is being dragged, uses {@link #BUBBLE_DRAG_ALPHA}. + * Falls back to {@link com.android.launcher3.LauncherAnimUtils#VIEW_ALPHA} otherwise. + */ + private FloatProperty getLocationAnimAlphaProperty() { + return mDraggedBubbleView == null ? VIEW_ALPHA : BUBBLE_DRAG_ALPHA; + } + + /** + * Set translation-x value for the bar while a bubble is being dragged. + * We can not update translation on the bar directly because the dragged bubble would be + * affected as well. As it is a child view. + * Instead, while a bubble is being dragged, set translation on each child view, that is not the + * dragged view. And set a translation on the background. + * This allows for the dragged bubble view to remain in position while the bar moves during + * animation. + */ + private void setTranslationXDuringBubbleDrag(float translationX) { + mTranslationXDuringDrag = translationX; + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + BubbleView view = (BubbleView) getChildAt(i); + if (view != mDraggedBubbleView) { + view.setBubbleBarTranslationX(translationX); + } + } + if (mBubbleBarBackground != null) { + mTempBackgroundBounds.set(mBubbleBarBackground.getBounds()); + mTempBackgroundBounds.offsetTo((int) translationX, 0); + mBubbleBarBackground.setBounds(mTempBackgroundBounds); + } + } + + /** + * Set alpha value for the bar while a bubble is being dragged. + * We can not update the alpha on the bar directly because the dragged bubble would be affected + * as well. As it is a child view. + * Instead, while a bubble is being dragged, set alpha on each child view, that is not the + * dragged view. And set an alpha on the background. + * This allows for the dragged bubble to remain visible while the bar is hidden during + * animation. + */ + private void setAlphaDuringBubbleDrag(float alpha) { + mAlphaDuringDrag = alpha; + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View view = getChildAt(i); + if (view != mDraggedBubbleView) { + view.setAlpha(alpha); + } + } + if (mBubbleBarBackground != null) { + mBubbleBarBackground.setAlpha((int) (255 * alpha)); + } + } + + private void resetDragAnimation() { + if (mBubbleBarLocationAnimator != null) { + mBubbleBarLocationAnimator.removeAllListeners(); + mBubbleBarLocationAnimator.cancel(); + mBubbleBarLocationAnimator = null; + } + setTranslationXDuringBubbleDrag(0f); + setAlphaDuringBubbleDrag(1f); + setTranslationX(0f); + setAlpha(1f); + } + /** * Updates the bounds with translation that may have been applied and returns the result. */ @@ -558,30 +702,21 @@ public class BubbleBarView extends FrameLayout { float elevationState = (1 - widthState); for (int i = 0; i < bubbleCount; i++) { BubbleView bv = (BubbleView) getChildAt(i); + if (bv == mDraggedBubbleView) { + // Skip the dragged bubble. Its translation is managed by the drag controller. + continue; + } bv.setTranslationY(ty); // the position of the bubble when the bar is fully expanded - final float expandedX; + final float expandedX = getExpandedBubbleTranslationX(i, bubbleCount, onLeft); // the position of the bubble when the bar is fully collapsed - final float collapsedX; - if (onLeft) { - // If bar is on the left, bubbles are ordered right to left - expandedX = (bubbleCount - i - 1) * (mIconSize + mExpandedBarIconsSpacing); - // Shift the first bubble only if there are more bubbles in addition to overflow - collapsedX = i == 0 && bubbleCount > 2 ? mIconOverlapAmount : 0; - } else { - // Bubbles ordered left to right, don't move the first bubble - expandedX = i * (mIconSize + mExpandedBarIconsSpacing); - collapsedX = i == 0 ? 0 : mIconOverlapAmount; - } - if (bv == mDraggedBubbleView) { - // if bubble is dragged set the elevation to bubble drag elevation - bv.setZ(mDragElevation); - } else { - // otherwise slowly animate elevation while keeping correct Z ordering - float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i; - bv.setZ(fullElevationForChild * elevationState); - } + final float collapsedX = getCollapsedBubbleTranslationX(i, bubbleCount, onLeft); + + // slowly animate elevation while keeping correct Z ordering + float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i; + bv.setZ(fullElevationForChild * elevationState); + if (mIsBarExpanded) { // If bar is on the right, account for bubble bar expanding and shifting left final float expandedBarShift = onLeft ? 0 : currentWidth - expandedWidth; @@ -601,9 +736,10 @@ public class BubbleBarView extends FrameLayout { // If we're fully collapsed, hide all bubbles except for the first 2. If there are // only 2 bubbles, hide the second bubble as well because it's the overflow. if (widthState == 0) { - if (i > 1) { + if (i > MAX_VISIBLE_BUBBLES_COLLAPSED - 1) { bv.setAlpha(0); - } else if (i == 1 && bubbleCount == 2) { + } else if (i == MAX_VISIBLE_BUBBLES_COLLAPSED - 1 + && bubbleCount == MAX_VISIBLE_BUBBLES_COLLAPSED) { bv.setAlpha(0); } } @@ -636,6 +772,34 @@ public class BubbleBarView extends FrameLayout { mBubbleBarBackground.setWidth(interpolatedWidth); } + private float getExpandedBubbleTranslationX(int bubbleIndex, int bubbleCount, + boolean onLeft) { + if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) { + return 0; + } + if (onLeft) { + // If bar is on the left, bubbles are ordered right to left + return (bubbleCount - bubbleIndex - 1) * (mIconSize + mExpandedBarIconsSpacing); + } else { + // Bubbles ordered left to right, don't move the first bubble + return bubbleIndex * (mIconSize + mExpandedBarIconsSpacing); + } + } + + private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount, + boolean onLeft) { + if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) { + return 0; + } + if (onLeft) { + // Shift the first bubble only if there are more bubbles in addition to overflow + return bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED + ? mIconOverlapAmount : 0; + } else { + return bubbleIndex == 0 ? 0 : mIconOverlapAmount; + } + } + /** * Reorders the views to match the provided list. */ @@ -685,8 +849,18 @@ public class BubbleBarView extends FrameLayout { * Sets the dragged bubble view to correctly apply Z order. Dragged view should appear on top */ public void setDraggedBubble(@Nullable BubbleView view) { + if (mDraggedBubbleView != null) { + mDraggedBubbleView.setZ(0); + if (view == null) { + // We are clearing the dragged bubble, reset drag + resetDragAnimation(); + } + } mDraggedBubbleView = view; - requestLayout(); + if (view != null) { + view.setZ(mDragElevation); + } + setIsDragging(view != null); } /** @@ -745,7 +919,7 @@ public class BubbleBarView extends FrameLayout { if (bubbleBarLocation.isOnLeft(isLayoutRtl())) { // Bubble positions are reversed. First bubble may be shifted, if there are more // bubbles than the current bubble and overflow. - bubblePosition = index == 0 && getChildCount() > 2 ? 1 : 0; + bubblePosition = index == 0 && getChildCount() > MAX_VISIBLE_BUBBLES_COLLAPSED ? 1 : 0; } else { bubblePosition = index; } @@ -807,7 +981,7 @@ public class BubbleBarView extends FrameLayout { final int horizontalPadding = getPaddingStart() + getPaddingEnd(); // If there are more than 2 bubbles, the first 2 should be visible when collapsed. // Otherwise just the first bubble should be visible because we don't show the overflow. - return childCount > 2 + return childCount > MAX_VISIBLE_BUBBLES_COLLAPSED ? mIconSize + mIconOverlapAmount + horizontalPadding : mIconSize + horizontalPadding; } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index dc48a66cd0..95dd24b13b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -20,6 +20,7 @@ import static android.view.View.VISIBLE; import android.content.res.Resources; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; import android.util.Log; @@ -468,7 +469,8 @@ public class BubbleBarViewController { */ public void onDragStart(@NonNull BubbleView bubbleView) { if (bubbleView.getBubble() == null) return; - mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ true); + + mSystemUiProxy.startBubbleDrag(bubbleView.getBubble().getKey()); mBarView.setDraggedBubble(bubbleView); } @@ -476,18 +478,45 @@ public class BubbleBarViewController { * Notifies SystemUI to expand the selected bubble when the bubble is released. * @param bubbleView dragged bubble view */ - public void onDragRelease(@NonNull BubbleView bubbleView) { + public void onDragRelease(@NonNull BubbleView bubbleView, BubbleBarLocation location) { if (bubbleView.getBubble() == null) return; - mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ false); + // TODO(b/330585402): send new bubble bar bounds to shell for the animation + mSystemUiProxy.stopBubbleDrag(bubbleView.getBubble().getKey(), location); } /** - * Removes the dragged bubble view in the bubble bar view + * Notifies {@link BubbleBarView} that drag and all animations are finished. */ public void onDragEnd() { mBarView.setDraggedBubble(null); } + /** + * Get translation for bubble bar when drag is released. + * + * @see BubbleBarView#getBubbleBarDragReleaseTranslation(PointF, BubbleBarLocation) + */ + public PointF getBubbleBarDragReleaseTranslation(PointF initialTranslation, + BubbleBarLocation location) { + if (location == mBarView.getBubbleBarLocation()) { + return initialTranslation; + } + return mBarView.getBubbleBarDragReleaseTranslation(initialTranslation, location); + } + + /** + * Get translation for bubble view when drag is released. + * + * @see BubbleBarView#getDraggedBubbleReleaseTranslation(PointF, BubbleBarLocation) + */ + public PointF getDraggedBubbleReleaseTranslation(PointF initialTranslation, + BubbleBarLocation location) { + if (location == mBarView.getBubbleBarLocation()) { + return initialTranslation; + } + return mBarView.getDraggedBubbleReleaseTranslation(initialTranslation, location); + } + /** * Called when bubble was dragged into the dismiss target. Notifies System * @param bubble dismissed bubble item diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java index 90f1be36cb..295477c963 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java @@ -30,6 +30,7 @@ public class BubbleControllers { public final BubbleDragController bubbleDragController; public final BubbleDismissController bubbleDismissController; public final BubbleBarPinController bubbleBarPinController; + public final BubblePinController bubblePinController; private final RunnableList mPostInitRunnables = new RunnableList(); @@ -45,7 +46,8 @@ public class BubbleControllers { BubbleStashedHandleViewController bubbleStashedHandleViewController, BubbleDragController bubbleDragController, BubbleDismissController bubbleDismissController, - BubbleBarPinController bubbleBarPinController) { + BubbleBarPinController bubbleBarPinController, + BubblePinController bubblePinController) { this.bubbleBarController = bubbleBarController; this.bubbleBarViewController = bubbleBarViewController; this.bubbleStashController = bubbleStashController; @@ -53,6 +55,7 @@ public class BubbleControllers { this.bubbleDragController = bubbleDragController; this.bubbleDismissController = bubbleDismissController; this.bubbleBarPinController = bubbleBarPinController; + this.bubblePinController = bubblePinController; } /** @@ -68,6 +71,7 @@ public class BubbleControllers { bubbleDragController.init(/* bubbleControllers = */ this); bubbleDismissController.init(/* bubbleControllers = */ this); bubbleBarPinController.init(this); + bubblePinController.init(this); mPostInitRunnables.executeAllAndDestroy(); } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java index d1c9da7655..1764f757cc 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java @@ -43,6 +43,7 @@ public class BubbleDragController { private BubbleBarViewController mBubbleBarViewController; private BubbleDismissController mBubbleDismissController; private BubbleBarPinController mBubbleBarPinController; + private BubblePinController mBubblePinController; public BubbleDragController(TaskbarActivityContext activity) { mActivity = activity; @@ -58,8 +59,12 @@ public class BubbleDragController { mBubbleBarViewController = bubbleControllers.bubbleBarViewController; mBubbleDismissController = bubbleControllers.bubbleDismissController; mBubbleBarPinController = bubbleControllers.bubbleBarPinController; + mBubblePinController = bubbleControllers.bubblePinController; mBubbleDismissController.setListener( - stuck -> mBubbleBarPinController.setDropTargetHidden(stuck)); + stuck -> { + mBubbleBarPinController.setDropTargetHidden(stuck); + mBubblePinController.setDropTargetHidden(stuck); + }); } /** @@ -73,19 +78,58 @@ public class BubbleDragController { } bubbleView.setOnTouchListener(new BubbleTouchListener() { + + private BubbleBarLocation mReleasedLocation = BubbleBarLocation.DEFAULT; + + private final LocationChangeListener mLocationChangeListener = + new LocationChangeListener() { + @Override + public void onChange(@NonNull BubbleBarLocation location) { + mBubbleBarController.animateBubbleBarLocation(location); + } + + @Override + public void onRelease(@NonNull BubbleBarLocation location) { + mReleasedLocation = location; + } + }; + @Override void onDragStart() { + mBubblePinController.setListener(mLocationChangeListener); mBubbleBarViewController.onDragStart(bubbleView); + mBubblePinController.onDragStart( + mBubbleBarViewController.getBubbleBarLocation().isOnLeft( + bubbleView.isLayoutRtl())); } @Override - void onDragEnd() { - mBubbleBarViewController.onDragEnd(); + protected void onDragUpdate(float x, float y) { + mBubblePinController.onDragUpdate(x, y); } @Override protected void onDragRelease() { - mBubbleBarViewController.onDragRelease(bubbleView); + mBubblePinController.onDragEnd(); + mBubbleBarViewController.onDragRelease(bubbleView, mReleasedLocation); + } + + @Override + protected void onDragDismiss() { + mBubblePinController.onDragEnd(); + } + + @Override + void onDragEnd() { + mBubbleBarController.updateBubbleBarLocation(mReleasedLocation); + mBubbleBarViewController.onDragEnd(); + mBubblePinController.setListener(null); + } + + @Override + protected PointF getRestingPosition() { + return mBubbleBarViewController.getDraggedBubbleReleaseTranslation( + getInitialPosition(), mReleasedLocation); } }); } @@ -98,8 +142,7 @@ public class BubbleDragController { PointF initialRelativePivot = new PointF(); bubbleBarView.setOnTouchListener(new BubbleTouchListener() { - @Nullable - private BubbleBarLocation mReleasedLocation; + private BubbleBarLocation mReleasedLocation = BubbleBarLocation.DEFAULT; private final LocationChangeListener mLocationChangeListener = new LocationChangeListener() { @@ -145,20 +188,18 @@ public class BubbleDragController { @Override void onDragEnd() { + // Make sure to update location as the first thing. Pivot update causes a relayout + mBubbleBarController.updateBubbleBarLocation(mReleasedLocation); + bubbleBarView.setIsDragging(false); // Restoring the initial pivot for the bubble bar view bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y); - bubbleBarView.setIsDragging(false); - mBubbleBarController.updateBubbleBarLocation(mReleasedLocation); + mBubbleBarPinController.setListener(null); } @Override protected PointF getRestingPosition() { - if (mReleasedLocation == null - || mReleasedLocation == bubbleBarView.getBubbleBarLocation()) { - return getInitialPosition(); - } - return bubbleBarView.getBubbleBarDragReleaseTranslation(getInitialPosition(), - mReleasedLocation); + return mBubbleBarViewController.getBubbleBarDragReleaseTranslation( + getInitialPosition(), mReleasedLocation); } }); } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt new file mode 100644 index 0000000000..fef7fa1dd5 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 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.bubbles + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Point +import android.view.Gravity.BOTTOM +import android.view.Gravity.LEFT +import android.view.Gravity.RIGHT +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import androidx.core.view.updateLayoutParams +import com.android.launcher3.R +import com.android.wm.shell.common.bubbles.BaseBubblePinController +import com.android.wm.shell.common.bubbles.BubbleBarLocation + +/** Controller to manage pinning bubble bar to left or right when dragging starts from a bubble */ +class BubblePinController( + private val context: Context, + private val container: FrameLayout, + screenSizeProvider: () -> Point +) : BaseBubblePinController(screenSizeProvider) { + + private lateinit var bubbleBarViewController: BubbleBarViewController + private lateinit var bubbleStashController: BubbleStashController + private var exclRectWidth: Float = 0f + private var exclRectHeight: Float = 0f + + private var dropTargetView: View? = null + private var dropTargetMargin: Int = 0 + + fun init(bubbleControllers: BubbleControllers) { + bubbleBarViewController = bubbleControllers.bubbleBarViewController + bubbleStashController = bubbleControllers.bubbleStashController + exclRectWidth = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width) + exclRectHeight = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_height) + dropTargetMargin = + context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_drop_target_margin) + } + + override fun getExclusionRectWidth(): Float { + return exclRectWidth + } + + override fun getExclusionRectHeight(): Float { + return exclRectHeight + } + + override fun getDropTargetView(): View? { + return dropTargetView + } + + override fun removeDropTargetView(view: View) { + container.removeView(view) + dropTargetView = null + } + + override fun createDropTargetView(): View { + return LayoutInflater.from(context) + .inflate(R.layout.bubble_expanded_view_drop_target, container, false) + .also { view -> + // TODO(b/330585402): dynamic height for the drop target based on actual height + dropTargetView = view + container.addView(view) + } + } + + @SuppressLint("RtlHardcoded") + override fun updateLocation(location: BubbleBarLocation) { + val onLeft = location.isOnLeft(container.isLayoutRtl) + + val bubbleBarBounds = bubbleBarViewController.bubbleBarBounds + dropTargetView?.updateLayoutParams { + gravity = BOTTOM or (if (onLeft) LEFT else RIGHT) + bottomMargin = + -bubbleStashController.bubbleBarTranslationY.toInt() + + bubbleBarBounds.height() + + dropTargetMargin + } + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java index bcdc718955..3dc4ebc5c4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java @@ -66,6 +66,9 @@ public class BubbleView extends ConstraintLayout { private final ImageView mAppIcon; private final int mBubbleSize; + private float mBubbleBarTranslationX = 0f; + private float mTranslationX = 0f; + private DotRenderer mDotRenderer; private DotRenderer.DrawParams mDrawParams; private int mDotColor; @@ -126,6 +129,35 @@ public class BubbleView extends ConstraintLayout { outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize); } + @Override + public void setTranslationX(float translationX) { + // Overriding setting translationX as it can be a combination of the parent translation + // and current view translation. + // When a BubbleView is being dragged to pin the bubble bar to other side, we animate the + // bar to the new location during the drag. + // One part of the animation is updating the translation of the bubble bar. But doing + // that also updates the translation for the child views, like the dragged bubble. + // To get around that, we instead apply translation on each child view of bubble bar. It + // is applied as bubble bar translation. This results in BubbleView's translation being a + // sum of the translation it has and the parent bubble bar translation. + mTranslationX = translationX; + applyTranslation(); + } + + /** + * Translation of the bubble bar that hosts this bubble. + * Is applied together with translation applied on the view through + * {@link #setTranslationX(float)}. + */ + void setBubbleBarTranslationX(float translationX) { + mBubbleBarTranslationX = translationX; + applyTranslation(); + } + + private void applyTranslation() { + super.setTranslationX(mTranslationX + mBubbleBarTranslationX); + } + @Override public void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index fcf5ffc389..0ad60b7f1e 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -800,15 +800,29 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle, SafeCloseable { /** * Tells SysUI when the bubble is being dragged. * Should be called only when the bubble bar is expanded. - * @param bubbleKey the key of the bubble to collapse/expand - * @param isBeingDragged whether the bubble is being dragged + * @param bubbleKey key of the bubble being dragged */ - public void onBubbleDrag(@Nullable String bubbleKey, boolean isBeingDragged) { + public void startBubbleDrag(@Nullable String bubbleKey) { if (mBubbles == null) return; try { - mBubbles.onBubbleDrag(bubbleKey, isBeingDragged); + mBubbles.startBubbleDrag(bubbleKey); } catch (RemoteException e) { - Log.w(TAG, "Failed call onBubbleDrag"); + Log.w(TAG, "Failed call startBubbleDrag"); + } + } + + /** + * Tells SysUI when the bubble stops being dragged. + * Should be called only when the bubble bar is expanded. + * @param bubbleKey key of the bubble being dragged + * @param location location of the bubble bar + */ + public void stopBubbleDrag(@Nullable String bubbleKey, BubbleBarLocation location) { + if (mBubbles == null) return; + try { + mBubbles.stopBubbleDrag(bubbleKey, location); + } catch (RemoteException e) { + Log.w(TAG, "Failed call stopBubbleDrag"); } }