diff --git a/quickstep/res/color/bubblebar_drop_target_bg_color.xml b/quickstep/res/color/bubblebar_drop_target_bg_color.xml
new file mode 100644
index 0000000000..ca37c7ffff
--- /dev/null
+++ b/quickstep/res/color/bubblebar_drop_target_bg_color.xml
@@ -0,0 +1,19 @@
+
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_bubble_bar_drop_target.xml b/quickstep/res/drawable/bg_bubble_bar_drop_target.xml
new file mode 100644
index 0000000000..79e43187f4
--- /dev/null
+++ b/quickstep/res/drawable/bg_bubble_bar_drop_target.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/quickstep/res/layout/bubble_bar_drop_target.xml b/quickstep/res/layout/bubble_bar_drop_target.xml
new file mode 100644
index 0000000000..23f240c67d
--- /dev/null
+++ b/quickstep/res/layout/bubble_bar_drop_target.xml
@@ -0,0 +1,21 @@
+
+
+
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 93ef735700..caa949eebf 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -417,6 +417,7 @@
80dp
1dp
+ 2dp
90dp
50dp
@@ -432,6 +433,11 @@
24dp
50dp
548dp
+ 192dp
+ 242dp
+
+
+ 36dp
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 26212c1934..b7a92eef7f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -98,6 +98,7 @@ import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.AutohideSu
import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
+import com.android.launcher3.taskbar.bubbles.BubbleBarPinController;
import com.android.launcher3.taskbar.bubbles.BubbleBarView;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
@@ -254,7 +255,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
new BubbleStashController(this),
new BubbleStashedHandleViewController(this, bubbleHandleView),
new BubbleDragController(this),
- new BubbleDismissController(this, mDragLayer)));
+ new BubbleDismissController(this, mDragLayer),
+ new BubbleBarPinController(this, mDragLayer,
+ () -> getDeviceProfile().getDisplayInfo().currentSize)
+ ));
}
// Construct controllers.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarPinController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarPinController.kt
new file mode 100644
index 0000000000..8ed994966c
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarPinController.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.graphics.RectF
+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 the bubble bar
+ */
+class BubbleBarPinController(
+ private val context: Context,
+ private val container: FrameLayout,
+ private val screenSizeProvider: () -> Point
+) : BaseBubblePinController() {
+
+ private lateinit var bubbleBarViewController: BubbleBarViewController
+ private lateinit var bubbleStashController: BubbleStashController
+ private var dropTargetView: View? = null
+
+ fun init(bubbleControllers: BubbleControllers) {
+ bubbleBarViewController = bubbleControllers.bubbleBarViewController
+ bubbleStashController = bubbleControllers.bubbleStashController
+ }
+
+ override fun getScreenCenterX(): Int {
+ return screenSizeProvider.invoke().x / 2
+ }
+
+ override fun getExclusionRect(): RectF {
+ val rect =
+ RectF(
+ 0f,
+ 0f,
+ context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width),
+ context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_height)
+ )
+ val screenSize = screenSizeProvider.invoke()
+ val middleX = screenSize.x / 2
+ // Center it around the bottom center of the screen
+ rect.offsetTo(middleX - rect.width() / 2, screenSize.y - rect.height())
+ return rect
+ }
+
+ override fun createDropTargetView(): View {
+ return LayoutInflater.from(context)
+ .inflate(R.layout.bubble_bar_drop_target, container, false)
+ .also { view ->
+ dropTargetView = view
+ container.addView(view)
+ }
+ }
+
+ override fun getDropTargetView(): View? {
+ return dropTargetView
+ }
+
+ override fun removeDropTargetView(view: View) {
+ container.removeView(view)
+ dropTargetView = null
+ }
+
+ @SuppressLint("RtlHardcoded")
+ override fun updateLocation(location: BubbleBarLocation) {
+ val onLeft = location.isOnLeft(container.isLayoutRtl)
+
+ val bounds = bubbleBarViewController.bubbleBarBounds
+ val horizontalMargin = bubbleBarViewController.horizontalMargin
+ dropTargetView?.updateLayoutParams {
+ width = bounds.width()
+ height = bounds.height()
+ gravity = BOTTOM or (if (onLeft) LEFT else RIGHT)
+ leftMargin = horizontalMargin
+ rightMargin = horizontalMargin
+ bottomMargin = -bubbleStashController.bubbleBarTranslationY.toInt()
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index c47427d4fb..90f1be36cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -29,6 +29,7 @@ public class BubbleControllers {
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
public final BubbleDragController bubbleDragController;
public final BubbleDismissController bubbleDismissController;
+ public final BubbleBarPinController bubbleBarPinController;
private final RunnableList mPostInitRunnables = new RunnableList();
@@ -43,13 +44,15 @@ public class BubbleControllers {
BubbleStashController bubbleStashController,
BubbleStashedHandleViewController bubbleStashedHandleViewController,
BubbleDragController bubbleDragController,
- BubbleDismissController bubbleDismissController) {
+ BubbleDismissController bubbleDismissController,
+ BubbleBarPinController bubbleBarPinController) {
this.bubbleBarController = bubbleBarController;
this.bubbleBarViewController = bubbleBarViewController;
this.bubbleStashController = bubbleStashController;
this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
this.bubbleDragController = bubbleDragController;
this.bubbleDismissController = bubbleDismissController;
+ this.bubbleBarPinController = bubbleBarPinController;
}
/**
@@ -64,6 +67,7 @@ public class BubbleControllers {
bubbleStashController.init(taskbarControllers, this);
bubbleDragController.init(/* bubbleControllers = */ this);
bubbleDismissController.init(/* bubbleControllers = */ this);
+ bubbleBarPinController.init(this);
mPostInitRunnables.executeAllAndDestroy();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
index 73c71c8edf..a40f33c595 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissController.java
@@ -67,6 +67,9 @@ public class BubbleDismissController {
@Nullable
private BubbleDragAnimator mAnimator;
+ @Nullable
+ private Listener mListener;
+
public BubbleDismissController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
mActivity = activity;
mDragLayer = dragLayer;
@@ -81,6 +84,13 @@ public class BubbleDismissController {
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
}
+ /**
+ * Set listener to be notified of dismiss events
+ */
+ public void setListener(@Nullable Listener listener) {
+ mListener = listener;
+ }
+
/**
* Setup the dismiss view and magnetized object that will be attracted to magnetic target.
* Should be called before handling events or showing/hiding dismiss view.
@@ -189,6 +199,9 @@ public class BubbleDismissController {
@NonNull MagnetizedObject> draggedObject) {
if (mAnimator == null) return;
mAnimator.animateDismissCaptured();
+ if (mListener != null) {
+ mListener.onStuckToDismissChanged(true /* stuck */);
+ }
}
@Override
@@ -197,6 +210,9 @@ public class BubbleDismissController {
float velX, float velY, boolean wasFlungOut) {
if (mAnimator == null) return;
mAnimator.animateDismissReleased();
+ if (mListener != null) {
+ mListener.onStuckToDismissChanged(false /* stuck */);
+ }
}
@Override
@@ -206,4 +222,10 @@ public class BubbleDismissController {
}
});
}
+
+ /** Interface to receive updates about the dismiss state */
+ public interface Listener {
+ /** Called when view is stuck or unstuck from dismiss target */
+ void onStuckToDismissChanged(boolean stuck);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
index 08fd681b4c..dab7d9d07a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -25,10 +25,11 @@ import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.R;
import com.android.launcher3.taskbar.TaskbarActivityContext;
/**
- * Controls bubble bar drag to dismiss interaction.
+ * Controls bubble bar drag interactions.
* Interacts with {@link BubbleDismissController}, used by {@link BubbleBarViewController}.
* Supported interactions:
* - Drag a single bubble view into dismiss target to remove it.
@@ -39,6 +40,7 @@ public class BubbleDragController {
private final TaskbarActivityContext mActivity;
private BubbleBarViewController mBubbleBarViewController;
private BubbleDismissController mBubbleDismissController;
+ private BubbleBarPinController mBubbleBarPinController;
public BubbleDragController(TaskbarActivityContext activity) {
mActivity = activity;
@@ -52,6 +54,12 @@ public class BubbleDragController {
public void init(@NonNull BubbleControllers bubbleControllers) {
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleDismissController = bubbleControllers.bubbleDismissController;
+ mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
+ mBubbleBarPinController.setListener(location -> {
+ // TODO(b/330585397): update bubble bar location in shell
+ });
+ mBubbleDismissController.setListener(
+ stuck -> mBubbleBarPinController.setDropTargetHidden(stuck));
}
/**
@@ -88,6 +96,10 @@ public class BubbleDragController {
@SuppressLint("ClickableViewAccessibility")
public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
PointF initialRelativePivot = new PointF();
+ final int restingElevation = bubbleBarView.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_elevation);
+ final int dragElevation = bubbleBarView.getResources().getDimensionPixelSize(
+ R.dimen.bubblebar_drag_elevation);
bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
@Override
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
@@ -102,12 +114,31 @@ public class BubbleDragController {
// By default the bubble bar view pivot is in bottom right corner, while dragging
// it should be centered in order to align it with the dismiss target view
bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
+ bubbleBarView.setElevation(dragElevation);
+ mBubbleBarPinController.onDragStart(
+ bubbleBarView.getBubbleBarLocation().isOnLeft(bubbleBarView.isLayoutRtl()));
+ }
+
+ @Override
+ protected void onDragUpdate(float x, float y) {
+ mBubbleBarPinController.onDragUpdate(x, y);
+ }
+
+ @Override
+ protected void onDragRelease() {
+ mBubbleBarPinController.onDragEnd();
+ }
+
+ @Override
+ protected void onDragDismiss() {
+ mBubbleBarPinController.onDragEnd();
}
@Override
void onDragEnd() {
// Restoring the initial pivot for the bubble bar view
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
+ bubbleBarView.setElevation(restingElevation);
}
});
}
@@ -169,6 +200,13 @@ public class BubbleDragController {
*/
abstract void onDragStart();
+ /**
+ * Called when bubble is dragged to new coordinates.
+ * Not called while bubble is stuck to the dismiss target.
+ */
+ protected void onDragUpdate(float x, float y) {
+ }
+
/**
* Called when the dragging interaction has ended and all the animations have completed
*/
@@ -232,8 +270,10 @@ public class BubbleDragController {
* @param event the motion event
*/
protected void onTouchMove(@NonNull View view, @NonNull MotionEvent event) {
- final float dx = event.getRawX() - mTouchDownLocation.x;
- final float dy = event.getRawY() - mTouchDownLocation.y;
+ float rawX = event.getRawX();
+ float rawY = event.getRawY();
+ final float dx = rawX - mTouchDownLocation.x;
+ final float dy = rawY - mTouchDownLocation.y;
switch (mState) {
case TOUCHED:
final boolean movedOut = Math.hypot(dx, dy) > mTouchSlop;
@@ -244,7 +284,7 @@ public class BubbleDragController {
}
break;
case DRAGGING:
- drag(view, event, dx, dy);
+ drag(view, event, dx, dy, rawX, rawY);
break;
}
}
@@ -293,10 +333,12 @@ public class BubbleDragController {
mBubbleDismissController.showDismissView();
}
- private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy) {
+ private void drag(@NonNull View view, @NonNull MotionEvent event, float dx, float dy,
+ float x, float y) {
if (mBubbleDismissController.handleTouchEvent(event)) return;
view.setTranslationX(mViewInitialPosition.x + dx);
view.setTranslationY(mViewInitialPosition.y + dy);
+ onDragUpdate(x, y);
}
private void stopDragging(@NonNull View view, @NonNull MotionEvent event) {