Merge changes I6fe1139e,I581dddf2 into main

* changes:
  Handle new bubble notification while drag is in progress.
  Made the bubble bar react to drag events from the Shell.
This commit is contained in:
Mykola Podolian
2025-02-18 14:03:17 -08:00
committed by Android (Google) Code Review
7 changed files with 226 additions and 24 deletions
@@ -52,6 +52,7 @@ import android.view.ViewRootImpl;
import android.window.SurfaceSyncGroup;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.internal.logging.InstanceId;
@@ -75,6 +76,7 @@ import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
@@ -517,6 +519,7 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
return mIsSystemDragInProgress;
}
@VisibleForTesting
private void maybeOnDragEnd() {
if (!isDragging()) {
((BubbleTextView) mDragObject.originalView).setIconDisabled(false);
@@ -524,17 +527,38 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false);
mActivity.onDragEnd();
if (mReturnAnimator == null) {
// If an item is dropped on the bubble bar, the bubble bar handles the drop,
// so it should not collapse along with the taskbar.
boolean droppedOnBubbleBar = notifyBubbleBarItemDropped();
// Upon successful drag, immediately stash taskbar.
// Note, this must be done last to ensure no AutohideSuspendFlags are active, as
// that will prevent us from stashing until the timeout.
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
/* stash = */ true,
/* shouldBubblesFollow = */ !droppedOnBubbleBar
);
mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
.log(LAUNCHER_APP_LAUNCH_DRAGDROP);
}
}
}
/**
* Exits the Bubble Bar drop target mode if applicable.
*
* @return {@code true} if drop target mode was active.
*/
private boolean notifyBubbleBarItemDropped() {
return mControllers.bubbleControllers.map(bc -> {
BubbleBarViewController bubbleBarViewController = bc.bubbleBarViewController;
boolean showingDropTarget = bubbleBarViewController.isShowingDropTarget();
if (showingDropTarget) {
bubbleBarViewController.onItemDroppedInBubbleBarDragZone();
}
return showingDropTarget;
}).orElse(false);
}
@Override
protected void endDrag() {
if (mDisallowGlobalDrag) {
@@ -44,10 +44,13 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
private val arrowVisibleHeight: Float
private val strokeAlpha: Int
private val strokeColor: Int
private val strokeColorDropTarget: Int
private val shadowAlpha: Int
private val shadowBlur: Float
private val keyShadowDistance: Float
private var arrowHeightFraction = 1f
private var isShowingDropTarget: Boolean = false
var arrowPositionX: Float = 0f
private set
@@ -100,7 +103,9 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
fillPaint.flags = Paint.ANTI_ALIAS_FLAG
fillPaint.style = Paint.Style.FILL
// configure stroke paint
strokePaint.color = context.getColor(R.color.taskbar_stroke)
strokeColor = context.getColor(R.color.taskbar_stroke)
strokeColorDropTarget = context.getColor(com.android.internal.R.color.system_primary_fixed)
strokePaint.color = strokeColor
strokePaint.flags = Paint.ANTI_ALIAS_FLAG
strokePaint.style = Paint.Style.STROKE
strokePaint.strokeWidth = res.getDimension(R.dimen.transient_taskbar_stroke_width)
@@ -235,9 +240,25 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
return max(0f, getScaledArrowHeight() - (arrowHeight - arrowVisibleHeight))
}
/** Set whether the background should show the drop target */
fun showDropTarget(isDropTarget: Boolean) {
if (isShowingDropTarget == isDropTarget) {
return
}
isShowingDropTarget = isDropTarget
val strokeColor = if (isDropTarget) strokeColorDropTarget else strokeColor
val alpha = if (isDropTarget) DRAG_STROKE_ALPHA else strokeAlpha
strokePaint.color = strokeColor
strokePaint.alpha = alpha
invalidateSelf()
}
fun isShowingDropTarget() = isShowingDropTarget
companion object {
private const val DARK_THEME_STROKE_ALPHA = 51
private const val LIGHT_THEME_STROKE_ALPHA = 41
private const val DRAG_STROKE_ALPHA = 255
private const val DARK_THEME_SHADOW_ALPHA = 51
private const val LIGHT_THEME_SHADOW_ALPHA = 25
}
@@ -33,7 +33,8 @@ import android.os.Bundle;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.android.launcher3.taskbar.TaskbarSharedState;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
@@ -589,15 +590,24 @@ public class BubbleBarController extends IBubblesListener.Stub {
}
@Override
public void onDragItemOverBubbleBarDragZone(BubbleBarLocation location) {
//TODO(b/388894910): add meaningful implementation
MAIN_EXECUTOR.execute(() ->
Toast.makeText(mContext, "onDragItemOver " + location, Toast.LENGTH_SHORT).show());
public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
MAIN_EXECUTOR.execute(() -> {
mBubbleBarViewController.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) {
mBubbleBarLocationListener.onBubbleBarLocationAnimated(bubbleBarLocation);
}
});
}
@Override
public void onItemDraggedOutsideBubbleBarDropZone() {
MAIN_EXECUTOR.execute(() -> {
if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) {
BubbleBarLocation original = mBubbleBarViewController.getBubbleBarLocation();
mBubbleBarLocationListener.onBubbleBarLocationAnimated(original);
}
mBubbleBarViewController.onItemDraggedOutsideBubbleBarDropZone();
});
}
/** Notifies WMShell to show the expanded view. */
@@ -536,6 +536,16 @@ public class BubbleBarView extends FrameLayout {
return (float) (displayWidth - getWidth() - margin);
}
/** Set whether the background should show the drop target */
public void showDropTarget(boolean isDropTarget) {
mBubbleBarBackground.showDropTarget(isDropTarget);
}
/** Returns whether the Bubble Bar is currently displaying a drop target. */
public boolean isShowingDropTarget() {
return mBubbleBarBackground.isShowingDropTarget();
}
/**
* Animate bubble bar to the given location transiently. Does not modify the layout or the value
* returned by {@link #getBubbleBarLocation()}.
@@ -131,12 +131,13 @@ public class BubbleBarViewController {
// Whether the bar is hidden when stashed
private boolean mHiddenForStashed;
private boolean mShouldShowEducation;
public boolean mOverflowAdded;
private boolean mIsLocationUpdatedForDropTarget = false;
private BubbleBarViewAnimator mBubbleBarViewAnimator;
private final FrameLayout mBubbleBarContainer;
private BubbleBarFlyoutController mBubbleBarFlyoutController;
private BubbleBarPinController mBubbleBarPinController;
private TaskbarSharedState mTaskbarSharedState;
private final TimeSource mTimeSource = System::currentTimeMillis;
private final int mTaskbarTranslationDelta;
@@ -166,6 +167,7 @@ public class BubbleBarViewController {
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleBarController = bubbleControllers.bubbleBarController;
mBubbleDragController = bubbleControllers.bubbleDragController;
mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
mTaskbarStashController = controllers.taskbarStashController;
mTaskbarInsetsController = controllers.taskbarInsetsController;
mBubbleBarFlyoutController = new BubbleBarFlyoutController(
@@ -274,7 +276,10 @@ public class BubbleBarViewController {
@Override
public boolean isOnLeft() {
return mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
boolean shouldRevertLocation =
mBarView.isShowingDropTarget() && mIsLocationUpdatedForDropTarget;
boolean isOnLeft = mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
return shouldRevertLocation != isOnLeft;
}
@Override
@@ -524,6 +529,61 @@ public class BubbleBarViewController {
mBarView.animateToBubbleBarLocation(bubbleBarLocation);
}
/** Returns whether the Bubble Bar is currently displaying a drop target. */
public boolean isShowingDropTarget() {
return mBarView.isShowingDropTarget();
}
/**
* Notifies the controller that a drag event is over the Bubble Bar drop zone. The controller
* will display the appropriate drop target and enter drop target mode. The controller will also
* update the return value of {@link #isLocationUpdatedForDropTarget()} to true if location was
* updated.
*/
public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
mBarView.showDropTarget(/* isDropTarget = */ true);
mIsLocationUpdatedForDropTarget = getBubbleBarLocation() != bubbleBarLocation;
if (mIsLocationUpdatedForDropTarget) {
animateBubbleBarLocation(bubbleBarLocation);
}
if (!hasBubbles()) {
mBubbleBarPinController.showDropTarget(bubbleBarLocation);
}
}
/**
* Returns {@code true} if location was updated after most recent
* {@link #onDragItemOverBubbleBarDragZone}}.
*/
public boolean isLocationUpdatedForDropTarget() {
return mIsLocationUpdatedForDropTarget;
}
/**
* Notifies the controller that the drag event is outside the Bubble Bar drop zone.
* This will hide the drop target zone if there are no bubbles or return the
* Bubble Bar to its original location. The controller will also exit drop target
* mode and reset the value returned from {@link #isLocationUpdatedForDropTarget()} to false.
*/
public void onItemDraggedOutsideBubbleBarDropZone() {
mBarView.showDropTarget(/* isDropTarget = */ false);
if (mIsLocationUpdatedForDropTarget) {
animateBubbleBarLocation(getBubbleBarLocation());
}
mBubbleBarPinController.hideDropTarget();
mIsLocationUpdatedForDropTarget = false;
}
/**
* Notifies the controller that the drag has completed over the Bubble Bar drop zone.
* The controller will hide the drop target if there are no bubbles and exit drop target mode.
*/
public void onItemDroppedInBubbleBarDragZone() {
mBarView.showDropTarget(/* isDropTarget = */ false);
mBubbleBarPinController.hideDropTarget();
mIsLocationUpdatedForDropTarget = false;
}
/**
* The bounds of the bubble bar.
*/
@@ -996,7 +1056,12 @@ public class BubbleBarViewController {
boolean isInApp = mTaskbarStashController.isInApp();
// if this is the first bubble, animate to the initial state.
if (mBarView.getBubbleChildCount() == 1 && !isUpdate) {
mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding);
// If a drop target is visible and the first bubble is added, hide the empty drop target
if (mBarView.isShowingDropTarget()) {
mBubbleBarPinController.hideDropTarget();
}
mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding,
mBarView.isShowingDropTarget());
return;
}
// if we're not stashed or we're in persistent taskbar, animate for collapsed state.
@@ -365,7 +365,12 @@ constructor(
}
/** Animates to the initial state of the bubble bar, when there are no previous bubbles. */
fun animateToInitialState(b: BubbleBarBubble, isInApp: Boolean, isExpanding: Boolean) {
fun animateToInitialState(
b: BubbleBarBubble,
isInApp: Boolean,
isExpanding: Boolean,
isDragging: Boolean = false,
) {
val bubbleView = b.view
val animator = PhysicsAnimator.getInstance(bubbleView)
if (animator.isRunning()) animator.cancel()
@@ -374,15 +379,12 @@ constructor(
// bubble bar to the handle if we're in an app.
val showAnimation = buildBubbleBarSpringInAnimation()
val hideAnimation =
if (isInApp && !isExpanding) {
if (isInApp && !isExpanding && !isDragging) {
buildBubbleBarToHandleAnimation()
} else {
Runnable {
moveToState(AnimatingBubble.State.ANIMATING_OUT)
bubbleBarFlyoutController.collapseFlyout {
onFlyoutRemoved()
clearAnimatingBubble()
}
collapseFlyoutAndUpdateState()
if (isDragging) return@Runnable
bubbleStashController.showBubbleBarImmediate()
bubbleStashController.updateTaskbarTouchRegion()
}
@@ -440,11 +442,7 @@ constructor(
// first bounce the bubble bar and show the flyout. Then hide the flyout.
val showAnimation = buildBubbleBarBounceAnimation()
val hideAnimation = Runnable {
moveToState(AnimatingBubble.State.ANIMATING_OUT)
bubbleBarFlyoutController.collapseFlyout {
onFlyoutRemoved()
clearAnimatingBubble()
}
collapseFlyoutAndUpdateState()
bubbleStashController.showBubbleBarImmediate()
bubbleStashController.updateTaskbarTouchRegion()
}
@@ -454,6 +452,14 @@ constructor(
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
}
private fun collapseFlyoutAndUpdateState() {
moveToState(AnimatingBubble.State.ANIMATING_OUT)
bubbleBarFlyoutController.collapseFlyout {
onFlyoutRemoved()
clearAnimatingBubble()
}
}
/**
* The bubble bar animation when it is collapsed is divided into 2 chained animations. The first
* animation is a regular accelerate animation that moves the bubble bar upwards. When it ends
@@ -60,6 +60,7 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -572,6 +573,71 @@ class BubbleBarViewAnimatorTest {
verify(bubbleStashController).stashBubbleBarImmediate()
}
@Test
fun animateToInitialState_whileDragging_inApp() {
setUpBubbleBar()
setUpBubbleStashController()
whenever(bubbleStashController.bubbleBarTranslationY)
.thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
val handle = View(context)
val handleAnimator = PhysicsAnimator.getInstance(handle)
whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
var notifiedBubbleBarVisible = false
val onBubbleBarVisible = Runnable { notifiedBubbleBarVisible = true }
val animator =
BubbleBarViewAnimator(
bubbleBarView,
bubbleStashController,
flyoutController,
bubbleBarParentViewController,
onExpanded = emptyRunnable,
onBubbleBarVisible = onBubbleBarVisible,
animatorScheduler,
)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
bubbleBarView.visibility = INVISIBLE
animator.animateToInitialState(
bubble,
isInApp = true,
isExpanding = false,
isDragging = true,
)
}
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
barAnimator.assertIsNotRunning()
assertThat(animator.isAnimating).isTrue()
assertThat(bubbleBarView.alpha).isEqualTo(1)
assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1)
waitForFlyoutToShow()
assertThat(animatorScheduler.delayedBlock).isNotNull()
InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
waitForFlyoutToHide()
InstrumentationRegistry.getInstrumentation().runOnMainSync {}
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2)
assertThat(animator.isAnimating).isFalse()
assertThat(bubbleBarView.alpha).isEqualTo(1)
assertThat(handle.translationY).isEqualTo(0)
assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
assertThat(notifiedBubbleBarVisible).isTrue()
verify(bubbleStashController, never()).stashBubbleBarImmediate()
}
@Test
fun animateToInitialState_inApp_autoExpanding() {
setUpBubbleBar()