diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index cd1eea2f0a..1471234925 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -501,6 +501,15 @@ public class TaskbarActivityContext extends BaseTaskbarContext { return enableTinyTaskbar() && mDeviceProfile.isPhone && mDeviceProfile.isTaskbarPresent; } + /** + * Returns {@code true} iff bubble bar is enabled (but not necessarily visible / + * containing bubbles). + */ + @Override + public boolean isBubbleBarEnabled() { + return getBubbleControllers() != null && BubbleBarController.isBubbleBarEnabled(); + } + /** * Returns if software keyboard is docked or input toolbar is placed at the taskbar area */ diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index 4fe4ace5d1..f66b350ec9 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -308,6 +308,15 @@ public class BubbleBarViewController { return mBarView.getBubbleBarBounds(); } + /** Checks that bubble bar is visible and that the motion event is within bounds. */ + public boolean isEventOverBubbleBar(MotionEvent event) { + if (!isBubbleBarVisible()) return false; + final Rect bounds = getBubbleBarBounds(); + final int bubbleBarTopOnScreen = mBarView.getRestingTopPositionOnScreen(); + final float x = event.getX(); + return event.getRawY() >= bubbleBarTopOnScreen && x >= bounds.left && x <= bounds.right; + } + /** Whether a new bubble is animating. */ public boolean isAnimatingNewBubble() { return mBubbleBarViewAnimator != null && mBubbleBarViewAnimator.isAnimating(); diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java index 8158fe79b4..6bfe8f40ae 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java @@ -298,15 +298,11 @@ public class BubbleStashedHandleViewController { } // the bounds of the handle only include the visible part, so we check that the Y coordinate - // is anywhere within the stashed taskbar height. - int top = mActivity.getDeviceProfile().heightPx - mStashedTaskbarHeight; - - return (int) ev.getRawY() >= top && containsX((int) ev.getRawX()); - } - - /** Checks if the given x coordinate is within the stashed handle bounds. */ - public boolean containsX(int x) { - return x >= mStashedHandleBounds.left && x <= mStashedHandleBounds.right; + // is anywhere within the stashed height of bubble bar (same as taskbar stashed height). + final int top = mActivity.getDeviceProfile().heightPx - mStashedTaskbarHeight; + final float x = ev.getRawX(); + return ev.getRawY() >= top && x >= mStashedHandleBounds.left + && x <= mStashedHandleBounds.right; } /** Set a bubble bar location */ diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java index f898e2f002..0185737c1d 100644 --- a/quickstep/src/com/android/quickstep/InputConsumer.java +++ b/quickstep/src/com/android/quickstep/InputConsumer.java @@ -40,6 +40,7 @@ public interface InputConsumer { int TYPE_STATUS_BAR = 1 << 13; int TYPE_CURSOR_HOVER = 1 << 14; int TYPE_NAV_HANDLE_LONG_PRESS = 1 << 15; + int TYPE_BUBBLE_BAR = 1 << 16; String[] NAMES = new String[] { "TYPE_NO_OP", // 0 @@ -58,6 +59,7 @@ public interface InputConsumer { "TYPE_STATUS_BAR", // 13 "TYPE_CURSOR_HOVER", // 14 "TYPE_NAV_HANDLE_LONG_PRESS", // 15 + "TYPE_BUBBLE_BAR", // 16 }; InputConsumer NO_OP = () -> TYPE_NO_OP; diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 88ab5283f2..2b5aa71125 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -97,6 +97,7 @@ import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.taskbar.TaskbarManager; import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks; +import com.android.launcher3.taskbar.bubbles.BubbleControllers; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.ResourceUtils; import com.android.launcher3.testing.shared.TestProtocol; @@ -109,6 +110,7 @@ import com.android.launcher3.util.ScreenOnTracker; import com.android.launcher3.util.TraceHelper; import com.android.quickstep.inputconsumers.AccessibilityInputConsumer; import com.android.quickstep.inputconsumers.AssistantInputConsumer; +import com.android.quickstep.inputconsumers.BubbleBarInputConsumer; import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer; import com.android.quickstep.inputconsumers.NavHandleLongPressInputConsumer; import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer; @@ -901,11 +903,14 @@ public class TouchInteractionService extends Service { boolean isOneHandedModeActive = mDeviceState.isOneHandedModeActive(); boolean isInSwipeUpTouchRegion = mRotationTouchHelper.isInSwipeUpTouchRegion(event); TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext(); + BubbleControllers bubbleControllers = tac != null ? tac.getBubbleControllers() : null; + boolean isOnBubbles = bubbleControllers != null + && BubbleBarInputConsumer.isEventOnBubbles(tac, event); if (isInSwipeUpTouchRegion && tac != null) { tac.closeKeyboardQuickSwitchView(); } if ((!isOneHandedModeActive && isInSwipeUpTouchRegion) - || isHoverActionWithoutConsumer) { + || isHoverActionWithoutConsumer || isOnBubbles) { reasonString.append(!isOneHandedModeActive && isInSwipeUpTouchRegion ? "one handed mode is not active and event is in swipe up region" : "isHoverActionWithoutConsumer == true") @@ -1085,6 +1090,15 @@ public class TouchInteractionService extends Service { private InputConsumer newConsumer( GestureState previousGestureState, GestureState newGestureState, MotionEvent event) { + TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext(); + BubbleControllers bubbleControllers = tac != null ? tac.getBubbleControllers() : null; + if (bubbleControllers != null && BubbleBarInputConsumer.isEventOnBubbles(tac, event)) { + InputConsumer consumer = new BubbleBarInputConsumer(this, bubbleControllers, + mInputMonitorCompat); + logInputConsumerSelectionReason(consumer, newCompoundString( + "event is on bubbles, creating new input consumer")); + return consumer; + } AnimatedFloat progressProxy = mSwipeUpProxyProvider.apply(mGestureState); if (progressProxy != null) { InputConsumer consumer = new ProgressDelegateInputConsumer( @@ -1149,7 +1163,6 @@ public class TouchInteractionService extends Service { } // If Taskbar is present, we listen for swipe or cursor hover events to unstash it. - TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext(); if (tac != null && !(base instanceof AssistantInputConsumer)) { // Present always on large screen or on small screen w/ flag boolean useTaskbarConsumer = tac.getDeviceProfile().isTaskbarPresent diff --git a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java new file mode 100644 index 0000000000..dbe20685d5 --- /dev/null +++ b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java @@ -0,0 +1,147 @@ +/* + * 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.quickstep.inputconsumers; + +import static android.view.MotionEvent.INVALID_POINTER_ID; + +import android.content.Context; +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.android.launcher3.taskbar.TaskbarActivityContext; +import com.android.launcher3.taskbar.bubbles.BubbleBarViewController; +import com.android.launcher3.taskbar.bubbles.BubbleControllers; +import com.android.launcher3.taskbar.bubbles.BubbleDragController; +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController; +import com.android.launcher3.testing.TestLogging; +import com.android.launcher3.testing.shared.TestProtocol; +import com.android.quickstep.InputConsumer; +import com.android.systemui.shared.system.InputMonitorCompat; + +/** + * Listens for touch events on the bubble bar. + */ +public class BubbleBarInputConsumer implements InputConsumer { + + private final BubbleStashController mBubbleStashController; + private final BubbleBarViewController mBubbleBarViewController; + private final BubbleDragController mBubbleDragController; + private final InputMonitorCompat mInputMonitorCompat; + + private boolean mSwipeUpOnBubbleHandle; + private boolean mPassedTouchSlop; + + private final int mTouchSlop; + private final PointF mDownPos = new PointF(); + private final PointF mLastPos = new PointF(); + private final long mTimeForTap; + private int mActivePointerId = INVALID_POINTER_ID; + + public BubbleBarInputConsumer(Context context, BubbleControllers bubbleControllers, + InputMonitorCompat inputMonitorCompat) { + mBubbleStashController = bubbleControllers.bubbleStashController; + mBubbleBarViewController = bubbleControllers.bubbleBarViewController; + mBubbleDragController = bubbleControllers.bubbleDragController; + mInputMonitorCompat = inputMonitorCompat; + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mTimeForTap = ViewConfiguration.getTapTimeout(); + } + + @Override + public int getType() { + return TYPE_BUBBLE_BAR; + } + + @Override + public void onMotionEvent(MotionEvent ev) { + final boolean isStashed = mBubbleStashController.isStashed(); + final int action = ev.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = ev.getPointerId(0); + mDownPos.set(ev.getX(), ev.getY()); + mLastPos.set(mDownPos); + break; + case MotionEvent.ACTION_MOVE: + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == INVALID_POINTER_ID) { + break; + } + mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); + + float dX = mLastPos.x - mDownPos.x; + float dY = mLastPos.y - mDownPos.y; + if (!mPassedTouchSlop) { + mPassedTouchSlop = Math.abs(dY) > mTouchSlop || Math.abs(dX) > mTouchSlop; + } + if ((isCollapsed() || isStashed) && !mSwipeUpOnBubbleHandle && mPassedTouchSlop) { + boolean verticalGesture = Math.abs(dY) > Math.abs(dX); + if (verticalGesture && !mBubbleDragController.isDragging()) { + mSwipeUpOnBubbleHandle = true; + mBubbleStashController.showBubbleBar(/* expandBubbles= */ true); + // Bubbles is handling the swipe so make sure no one else gets it. + TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); + mInputMonitorCompat.pilferPointers(); + } + } + break; + case MotionEvent.ACTION_UP: + boolean isWithinTapTime = ev.getEventTime() - ev.getDownTime() <= mTimeForTap; + if (isWithinTapTime && !mSwipeUpOnBubbleHandle && !mPassedTouchSlop) { + // Taps on the handle / collapsed state should open the bar + if (isStashed || isCollapsed()) { + mBubbleStashController.showBubbleBar(/* expandBubbles= */ true); + } + } + break; + } + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + cleanupAfterMotionEvent(); + } + } + + private void cleanupAfterMotionEvent() { + mPassedTouchSlop = false; + mSwipeUpOnBubbleHandle = false; + } + + private boolean isCollapsed() { + return mBubbleStashController.isBubbleBarVisible() + && !mBubbleBarViewController.isExpanded(); + } + + /** + * Returns whether the event is occurring on a visible bubble bar or the bar handle. + */ + public static boolean isEventOnBubbles(TaskbarActivityContext tac, MotionEvent ev) { + if (tac == null || !tac.isBubbleBarEnabled()) { + return false; + } + BubbleControllers controllers = tac.getBubbleControllers(); + if (controllers == null || !controllers.bubbleBarViewController.hasBubbles()) { + return false; + } + if (controllers.bubbleStashController.isStashed() + && controllers.bubbleStashedHandleViewController.isPresent()) { + return controllers.bubbleStashedHandleViewController.get().isEventOverHandle(ev); + } else if (controllers.bubbleBarViewController.isBubbleBarVisible()) { + return controllers.bubbleBarViewController.isEventOverBubbleBar(ev); + } + return false; + } +} diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java index 9a99d4a448..17a97fae21 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java @@ -15,9 +15,7 @@ */ package com.android.quickstep.inputconsumers; -import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_MOVE; -import static android.view.MotionEvent.ACTION_UP; import static android.view.MotionEvent.INVALID_POINTER_ID; import static com.android.launcher3.Flags.enableCursorHoverStates; @@ -43,7 +41,6 @@ import com.android.launcher3.R; import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.taskbar.TaskbarThresholdUtils; import com.android.launcher3.taskbar.TaskbarTranslationController.TransitionCallback; -import com.android.launcher3.taskbar.bubbles.BubbleControllers; import com.android.launcher3.touch.OverScroll; import com.android.launcher3.util.DisplayController; import com.android.quickstep.GestureState; @@ -69,9 +66,6 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer { private final int mTaskbarNavThresholdY; private final boolean mIsTaskbarAllAppsOpen; private boolean mHasPassedTaskbarNavThreshold; - private boolean mIsInBubbleBarArea; - private boolean mIsVerticalGestureOverBubbleBar; - private boolean mIsPassedBubbleBarSlop; private final int mTouchSlop; private final PointF mDownPos = new PointF(); @@ -159,9 +153,6 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer { if (mTransitionCallback != null && !mIsTaskbarAllAppsOpen) { mTransitionCallback.onActionDown(); } - if (mIsTransientTaskbar && isInBubbleBarArea(x)) { - mIsInBubbleBarArea = true; - } break; case MotionEvent.ACTION_POINTER_UP: int ptrIdx = ev.getActionIndex(); @@ -185,18 +176,6 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer { float dX = mLastPos.x - mDownPos.x; float dY = mLastPos.y - mDownPos.y; - if (!mIsPassedBubbleBarSlop && mIsInBubbleBarArea) { - boolean passedSlop = - Math.abs(dY) > mTouchSlop || Math.abs(dX) > mTouchSlop; - if (passedSlop) { - mIsPassedBubbleBarSlop = true; - mIsVerticalGestureOverBubbleBar = Math.abs(dY) > Math.abs(dX); - if (mIsVerticalGestureOverBubbleBar) { - setActive(ev); - } - } - } - if (mIsTransientTaskbar) { boolean passedTaskbarNavThreshold = dY < 0 && Math.abs(dY) >= mTaskbarNavThreshold; @@ -204,11 +183,7 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer { if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold && !mGestureState.isInExtendedSlopRegion()) { mHasPassedTaskbarNavThreshold = true; - if (mIsInBubbleBarArea && mIsVerticalGestureOverBubbleBar) { - mTaskbarActivityContext.onSwipeToOpenBubblebar(); - } else { - mTaskbarActivityContext.onSwipeToUnstashTaskbar(); - } + mTaskbarActivityContext.onSwipeToUnstashTaskbar(); } if (dY < 0) { @@ -230,41 +205,8 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer { break; } } - boolean isMovingInBubbleBarArea = mIsInBubbleBarArea && ev.getAction() == ACTION_MOVE; if (!isStashedTaskbarHovered) { - // if we're moving in the bubble bar area but we haven't passed the slop yet, don't - // propagate to the delegate, until we can determine the direction of the gesture. - if (!isMovingInBubbleBarArea || mIsPassedBubbleBarSlop) { - mDelegate.onMotionEvent(ev); - } - } - } else if (mIsVerticalGestureOverBubbleBar) { - // if we get here then this gesture is a vertical swipe over the bubble bar. - // we're also active and there's no need to delegate any additional motion events. the - // rest of the gesture will be handled here. - switch (ev.getAction()) { - case ACTION_MOVE: - int pointerIndex = ev.findPointerIndex(mActivePointerId); - if (pointerIndex == INVALID_POINTER_ID) { - break; - } - mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); - - float dY = mLastPos.y - mDownPos.y; - - // bubble bar swipe gesture uses the same threshold as the taskbar. - boolean passedTaskbarNavThreshold = dY < 0 - && Math.abs(dY) >= mTaskbarNavThreshold; - - if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) { - mHasPassedTaskbarNavThreshold = true; - mTaskbarActivityContext.onSwipeToOpenBubblebar(); - } - break; - case ACTION_UP: - case ACTION_CANCEL: - cleanupAfterMotionEvent(); - break; + mDelegate.onMotionEvent(ev); } } } @@ -301,9 +243,6 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer { mTransitionCallback.onActionEnd(); } mHasPassedTaskbarNavThreshold = false; - mIsInBubbleBarArea = false; - mIsVerticalGestureOverBubbleBar = false; - mIsPassedBubbleBarSlop = false; if (mVelocityTracker != null) { mVelocityTracker.recycle(); @@ -313,23 +252,6 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer { mMotionMoveCount = 0; } - private boolean isInBubbleBarArea(float x) { - if (mTaskbarActivityContext == null || !mIsTransientTaskbar) { - return false; - } - BubbleControllers controllers = mTaskbarActivityContext.getBubbleControllers(); - if (controllers == null) { - return false; - } - if (controllers.bubbleStashController.isStashed() - && controllers.bubbleStashedHandleViewController.isPresent()) { - return controllers.bubbleStashedHandleViewController.get().containsX((int) x); - } else { - Rect bubbleBarBounds = controllers.bubbleBarViewController.getBubbleBarBounds(); - return x >= bubbleBarBounds.left && x <= bubbleBarBounds.right; - } - } - /** * Listen for hover events for the stashed taskbar. * diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarInputConsumerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarInputConsumerTest.kt new file mode 100644 index 0000000000..785ec66420 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/BubbleBarInputConsumerTest.kt @@ -0,0 +1,156 @@ +/* + * 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.view.MotionEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.taskbar.TaskbarActivityContext +import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController +import com.android.quickstep.inputconsumers.BubbleBarInputConsumer +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.whenever + +/** + * Tests for bubble bar input consumer, namely the static method that indicates whether the input + * consumer should handle the event. + */ +@RunWith(AndroidJUnit4::class) +class BubbleBarInputConsumerTest { + + private lateinit var bubbleControllers: BubbleControllers + + @Mock private lateinit var taskbarActivityContext: TaskbarActivityContext + @Mock private lateinit var bubbleBarController: BubbleBarController + @Mock private lateinit var bubbleBarViewController: BubbleBarViewController + @Mock private lateinit var bubbleStashController: BubbleStashController + @Mock private lateinit var bubbleStashedHandleViewController: BubbleStashedHandleViewController + @Mock private lateinit var bubbleDragController: BubbleDragController + @Mock private lateinit var bubbleDismissController: BubbleDismissController + @Mock private lateinit var bubbleBarPinController: BubbleBarPinController + @Mock private lateinit var bubblePinController: BubblePinController + @Mock private lateinit var bubbleCreator: BubbleCreator + + @Mock private lateinit var motionEvent: MotionEvent + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + bubbleControllers = + BubbleControllers( + bubbleBarController, + bubbleBarViewController, + bubbleStashController, + Optional.of(bubbleStashedHandleViewController), + bubbleDragController, + bubbleDismissController, + bubbleBarPinController, + bubblePinController, + bubbleCreator + ) + } + + @Test + fun testIsEventOnBubbles_noTaskbarActivityContext() { + assertThat(BubbleBarInputConsumer.isEventOnBubbles(null, motionEvent)).isFalse() + } + + @Test + fun testIsEventOnBubbles_bubblesNotEnabled() { + whenever(taskbarActivityContext.isBubbleBarEnabled).thenReturn(false) + assertThat(BubbleBarInputConsumer.isEventOnBubbles(taskbarActivityContext, motionEvent)) + .isFalse() + } + + @Test + fun testIsEventOnBubbles_noBubbleControllers() { + whenever(taskbarActivityContext.isBubbleBarEnabled).thenReturn(true) + whenever(taskbarActivityContext.bubbleControllers).thenReturn(null) + assertThat(BubbleBarInputConsumer.isEventOnBubbles(taskbarActivityContext, motionEvent)) + .isFalse() + } + + @Test + fun testIsEventOnBubbles_noBubbles() { + whenever(taskbarActivityContext.isBubbleBarEnabled).thenReturn(true) + whenever(taskbarActivityContext.bubbleControllers).thenReturn(bubbleControllers) + whenever(bubbleBarViewController.hasBubbles()).thenReturn(false) + assertThat(BubbleBarInputConsumer.isEventOnBubbles(taskbarActivityContext, motionEvent)) + .isFalse() + } + + @Test + fun testIsEventOnBubbles_eventOnStashedHandle() { + whenever(taskbarActivityContext.isBubbleBarEnabled).thenReturn(true) + whenever(taskbarActivityContext.bubbleControllers).thenReturn(bubbleControllers) + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + whenever(bubbleStashController.isStashed).thenReturn(true) + whenever(bubbleStashedHandleViewController.isEventOverHandle(any())).thenReturn(true) + + assertThat(BubbleBarInputConsumer.isEventOnBubbles(taskbarActivityContext, motionEvent)) + .isTrue() + } + + @Test + fun testIsEventOnBubbles_eventNotOnStashedHandle() { + whenever(taskbarActivityContext.isBubbleBarEnabled).thenReturn(true) + whenever(taskbarActivityContext.bubbleControllers).thenReturn(bubbleControllers) + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + whenever(bubbleStashController.isStashed).thenReturn(true) + whenever(bubbleStashedHandleViewController.isEventOverHandle(any())).thenReturn(false) + + assertThat(BubbleBarInputConsumer.isEventOnBubbles(taskbarActivityContext, motionEvent)) + .isFalse() + } + + @Test + fun testIsEventOnBubbles_eventOnVisibleBubbleView() { + whenever(taskbarActivityContext.isBubbleBarEnabled).thenReturn(true) + whenever(taskbarActivityContext.bubbleControllers).thenReturn(bubbleControllers) + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + whenever(bubbleStashController.isStashed).thenReturn(false) + whenever(bubbleBarViewController.isBubbleBarVisible).thenReturn(true) + whenever(bubbleBarViewController.isEventOverBubbleBar(any())).thenReturn(true) + + assertThat(BubbleBarInputConsumer.isEventOnBubbles(taskbarActivityContext, motionEvent)) + .isTrue() + } + + @Test + fun testIsEventOnBubbles_eventNotOnVisibleBubbleView() { + whenever(taskbarActivityContext.isBubbleBarEnabled).thenReturn(true) + whenever(taskbarActivityContext.bubbleControllers).thenReturn(bubbleControllers) + whenever(bubbleBarViewController.hasBubbles()).thenReturn(true) + + whenever(bubbleStashController.isStashed).thenReturn(false) + whenever(bubbleBarViewController.isBubbleBarVisible).thenReturn(true) + whenever(bubbleBarViewController.isEventOverBubbleBar(any())).thenReturn(false) + + assertThat(BubbleBarInputConsumer.isEventOnBubbles(taskbarActivityContext, motionEvent)) + .isFalse() + } +}