Merge "Drag bubble in bubble bar to other side" into main

This commit is contained in:
Ats Jenk
2024-05-03 16:41:55 +00:00
committed by Android (Google) Code Review
12 changed files with 517 additions and 79 deletions
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/bubble_expanded_view_drop_target_corner_radius" />
<solid android:color="@color/bubblebar_drop_target_bg_color" />
<stroke
android:width="1dp"
android:color="?androidprv:attr/materialColorPrimaryContainer" />
</shape>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<!-- TODO(b/330585402): replace 600dp height with calculated value -->
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/bubble_expanded_view_drop_target_width"
android:layout_height="600dp"
android:layout_margin="@dimen/bubble_expanded_view_drop_target_margin"
android:background="@drawable/bg_bubble_expanded_view_drop_target"
android:elevation="@dimen/bubblebar_elevation" />
+3
View File
@@ -456,6 +456,9 @@
<!-- Bubble bar drop target -->
<dimen name="bubblebar_drop_target_corner_radius">36dp</dimen>
<dimen name="bubble_expanded_view_drop_target_corner_radius">16dp</dimen>
<dimen name="bubble_expanded_view_drop_target_width">412dp</dimen>
<dimen name="bubble_expanded_view_drop_target_margin">16dp</dimen>
<!-- Launcher splash screen -->
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
@@ -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)
));
}
@@ -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)
}
@@ -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<BubbleBarView> 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<BubbleBarView> 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<? super BubbleBarView> 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<? super BubbleBarView> 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<? super BubbleBarView> 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;
}
@@ -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
@@ -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();
}
@@ -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);
}
});
}
@@ -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<FrameLayout.LayoutParams> {
gravity = BOTTOM or (if (onLeft) LEFT else RIGHT)
bottomMargin =
-bubbleStashController.bubbleBarTranslationY.toInt() +
bubbleBarBounds.height() +
dropTargetMargin
}
}
}
@@ -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);
@@ -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");
}
}