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 a4b6ad0e57..e5396eec7b 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"); } }