summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetControllerTest.kt180
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt263
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetController.kt151
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt179
7 files changed, 569 insertions, 417 deletions
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetControllerTest.kt
deleted file mode 100644
index 2ac77917a348..000000000000
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetControllerTest.kt
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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.wm.shell.bubbles.bar
-
-import android.content.Context
-import android.graphics.Insets
-import android.graphics.Rect
-import android.view.View
-import android.view.WindowManager
-import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.internal.protolog.common.ProtoLog
-import com.android.wm.shell.R
-import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.bubbles.DeviceConfig
-import com.android.wm.shell.bubbles.bar.BubbleBarDropTargetController.Companion.DROP_TARGET_ALPHA_IN_DURATION
-import com.android.wm.shell.bubbles.bar.BubbleBarDropTargetController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
-import com.android.wm.shell.bubbles.bar.BubbleBarDropTargetController.Companion.DROP_TARGET_SCALE
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.ClassRule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** Tests for [BubbleBarDropTargetController] */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BubbleBarDropTargetControllerTest {
-
- companion object {
- @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
- }
-
- private val context = ApplicationProvider.getApplicationContext<Context>()
- private lateinit var controller: BubbleBarDropTargetController
- private lateinit var positioner: BubblePositioner
- private lateinit var container: FrameLayout
-
- @Before
- fun setUp() {
- ProtoLog.REQUIRE_PROTOLOGTOOL = false
- container = FrameLayout(context)
- val windowManager = context.getSystemService(WindowManager::class.java)
- positioner = BubblePositioner(context, windowManager)
- positioner.setShowingInBubbleBar(true)
- val deviceConfig =
- DeviceConfig(
- windowBounds = Rect(0, 0, 2000, 2600),
- isLargeScreen = true,
- isSmallTablet = false,
- isLandscape = true,
- isRtl = false,
- insets = Insets.of(10, 20, 30, 40)
- )
- positioner.update(deviceConfig)
- positioner.bubbleBarBounds = Rect(1800, 2400, 1970, 2560)
-
- controller = BubbleBarDropTargetController(context, container, positioner)
- }
-
- @Test
- fun show_moveLeftToRight_isVisibleWithExpectedBounds() {
- val expectedBoundsOnLeft = getExpectedDropTargetBounds(onLeft = true)
- val expectedBoundsOnRight = getExpectedDropTargetBounds(onLeft = false)
-
- runOnMainSync { controller.show(BubbleBarLocation.LEFT) }
- waitForAnimateIn()
- val viewOnLeft = getDropTargetView()
- assertThat(viewOnLeft).isNotNull()
- assertThat(viewOnLeft!!.alpha).isEqualTo(1f)
- assertThat(viewOnLeft.layoutParams.width).isEqualTo(expectedBoundsOnLeft.width())
- assertThat(viewOnLeft.layoutParams.height).isEqualTo(expectedBoundsOnLeft.height())
- assertThat(viewOnLeft.x).isEqualTo(expectedBoundsOnLeft.left)
- assertThat(viewOnLeft.y).isEqualTo(expectedBoundsOnLeft.top)
-
- runOnMainSync { controller.show(BubbleBarLocation.RIGHT) }
- waitForAnimateOut()
- waitForAnimateIn()
- val viewOnRight = getDropTargetView()
- assertThat(viewOnRight).isNotNull()
- assertThat(viewOnRight!!.alpha).isEqualTo(1f)
- assertThat(viewOnRight.layoutParams.width).isEqualTo(expectedBoundsOnRight.width())
- assertThat(viewOnRight.layoutParams.height).isEqualTo(expectedBoundsOnRight.height())
- assertThat(viewOnRight.x).isEqualTo(expectedBoundsOnRight.left)
- assertThat(viewOnRight.y).isEqualTo(expectedBoundsOnRight.top)
- }
-
- @Test
- fun toggleSetHidden_dropTargetShown_updatesAlpha() {
- runOnMainSync { controller.show(BubbleBarLocation.RIGHT) }
- waitForAnimateIn()
- val view = getDropTargetView()
- assertThat(view).isNotNull()
- assertThat(view!!.alpha).isEqualTo(1f)
-
- runOnMainSync { controller.setHidden(true) }
- waitForAnimateOut()
- val hiddenView = getDropTargetView()
- assertThat(hiddenView).isNotNull()
- assertThat(hiddenView!!.alpha).isEqualTo(0f)
-
- runOnMainSync { controller.setHidden(false) }
- waitForAnimateIn()
- val shownView = getDropTargetView()
- assertThat(shownView).isNotNull()
- assertThat(shownView!!.alpha).isEqualTo(1f)
- }
-
- @Test
- fun toggleSetHidden_dropTargetNotShown_viewNotCreated() {
- runOnMainSync { controller.setHidden(true) }
- waitForAnimateOut()
- assertThat(getDropTargetView()).isNull()
- runOnMainSync { controller.setHidden(false) }
- waitForAnimateIn()
- assertThat(getDropTargetView()).isNull()
- }
-
- @Test
- fun dismiss_dropTargetShown_viewRemoved() {
- runOnMainSync { controller.show(BubbleBarLocation.LEFT) }
- waitForAnimateIn()
- assertThat(getDropTargetView()).isNotNull()
- runOnMainSync { controller.dismiss() }
- waitForAnimateOut()
- assertThat(getDropTargetView()).isNull()
- }
-
- @Test
- fun dismiss_dropTargetNotShown_doesNothing() {
- runOnMainSync { controller.dismiss() }
- waitForAnimateOut()
- assertThat(getDropTargetView()).isNull()
- }
-
- private fun getDropTargetView(): View? = container.findViewById(R.id.bubble_bar_drop_target)
-
- private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect {
- val rect = Rect()
- positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, rect)
- // Scale the rect to expected size, but keep the center point the same
- val centerX = rect.centerX()
- val centerY = rect.centerY()
- rect.scale(DROP_TARGET_SCALE)
- rect.offset(centerX - rect.centerX(), centerY - rect.centerY())
- return rect
- }
-
- private fun runOnMainSync(runnable: Runnable) {
- InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable)
- }
-
- private fun waitForAnimateIn() {
- // Advance animator for on-device test
- runOnMainSync { animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_IN_DURATION) }
- }
-
- private fun waitForAnimateOut() {
- // Advance animator for on-device test
- runOnMainSync { animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_OUT_DURATION) }
- }
-}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
new file mode 100644
index 000000000000..e1bf40ca19dc
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -0,0 +1,263 @@
+/*
+ * 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.wm.shell.bubbles.bar
+
+import android.content.Context
+import android.graphics.Insets
+import android.graphics.PointF
+import android.graphics.Rect
+import android.view.View
+import android.view.WindowManager
+import android.widget.FrameLayout
+import androidx.core.animation.AnimatorTestRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.bubbles.DeviceConfig
+import com.android.wm.shell.bubbles.bar.BubbleExpandedViewPinController.Companion.DROP_TARGET_SCALE
+import com.android.wm.shell.common.bubbles.BaseBubblePinController
+import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
+import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests for [BubbleExpandedViewPinController] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleExpandedViewPinControllerTest {
+
+ companion object {
+ @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
+
+ const val SCREEN_WIDTH = 2000
+ const val SCREEN_HEIGHT = 1000
+
+ const val BUBBLE_BAR_WIDTH = 100
+ const val BUBBLE_BAR_HEIGHT = 50
+ }
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var positioner: BubblePositioner
+ private lateinit var container: FrameLayout
+
+ private lateinit var controller: BubbleExpandedViewPinController
+ private lateinit var testListener: TestLocationChangeListener
+
+ private val pointOnLeft = PointF(100f, 100f)
+ private val pointOnRight = PointF(1900f, 500f)
+
+ @Before
+ fun setUp() {
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ container = FrameLayout(context)
+ val windowManager = context.getSystemService(WindowManager::class.java)
+ positioner = BubblePositioner(context, windowManager)
+ positioner.setShowingInBubbleBar(true)
+ val deviceConfig =
+ DeviceConfig(
+ windowBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
+ isLargeScreen = true,
+ isSmallTablet = false,
+ isLandscape = true,
+ isRtl = false,
+ insets = Insets.of(10, 20, 30, 40)
+ )
+ positioner.update(deviceConfig)
+ positioner.bubbleBarBounds =
+ Rect(
+ SCREEN_WIDTH - deviceConfig.insets.right - BUBBLE_BAR_WIDTH,
+ SCREEN_HEIGHT - deviceConfig.insets.bottom - BUBBLE_BAR_HEIGHT,
+ SCREEN_WIDTH - deviceConfig.insets.right,
+ SCREEN_HEIGHT - deviceConfig.insets.bottom
+ )
+
+ controller = BubbleExpandedViewPinController(context, container, positioner)
+ testListener = TestLocationChangeListener()
+ controller.setListener(testListener)
+ }
+
+ @After
+ fun tearDown() {
+ runOnMainSync { controller.onDragEnd() }
+ waitForAnimateOut()
+ }
+
+ @Test
+ fun onDragUpdate_stayOnSameSide() {
+ runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNull()
+ assertThat(testListener.locationChanges).isEmpty()
+ }
+
+ @Test
+ fun onDragUpdate_toLeft() {
+ runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ }
+ waitForAnimateIn()
+
+ assertThat(dropTargetView).isNotNull()
+ assertThat(dropTargetView!!.alpha).isEqualTo(1f)
+
+ val expectedDropTargetBounds = getExpectedDropTargetBounds(onLeft = true)
+ assertThat(dropTargetView!!.layoutParams.width).isEqualTo(expectedDropTargetBounds.width())
+ assertThat(dropTargetView!!.layoutParams.height)
+ .isEqualTo(expectedDropTargetBounds.height())
+
+ assertThat(testListener.locationChanges).containsExactly(BubbleBarLocation.LEFT)
+ }
+
+ @Test
+ fun onDragUpdate_toLeftAndBackToRight() {
+ runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNotNull()
+
+ runOnMainSync { controller.onDragUpdate(pointOnRight.x, pointOnRight.y) }
+ // We have to wait for existing drop target to animate out and new to animate in
+ waitForAnimateOut()
+ waitForAnimateIn()
+
+ assertThat(dropTargetView).isNotNull()
+ assertThat(dropTargetView!!.alpha).isEqualTo(1f)
+
+ val expectedDropTargetBounds = getExpectedDropTargetBounds(onLeft = false)
+ assertThat(dropTargetView!!.layoutParams.width).isEqualTo(expectedDropTargetBounds.width())
+ assertThat(dropTargetView!!.layoutParams.height)
+ .isEqualTo(expectedDropTargetBounds.height())
+
+ assertThat(testListener.locationChanges)
+ .containsExactly(BubbleBarLocation.LEFT, BubbleBarLocation.RIGHT)
+ }
+
+ @Test
+ fun onDragUpdate_toLeftInExclusionRect() {
+ runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = false)
+ // Exclusion rect is around the bottom center area of the screen
+ controller.onDragUpdate(SCREEN_WIDTH / 2f - 50, SCREEN_HEIGHT - 100f)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNull()
+ assertThat(testListener.locationChanges).isEmpty()
+ }
+
+ @Test
+ fun toggleSetDropTargetHidden_dropTargetExists() {
+ runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ }
+ waitForAnimateIn()
+
+ assertThat(dropTargetView).isNotNull()
+ assertThat(dropTargetView!!.alpha).isEqualTo(1f)
+
+ runOnMainSync { controller.setDropTargetHidden(true) }
+ waitForAnimateOut()
+ assertThat(dropTargetView).isNotNull()
+ assertThat(dropTargetView!!.alpha).isEqualTo(0f)
+
+ runOnMainSync { controller.setDropTargetHidden(false) }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNotNull()
+ assertThat(dropTargetView!!.alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun toggleSetDropTargetHidden_noDropTarget() {
+ runOnMainSync { controller.setDropTargetHidden(true) }
+ waitForAnimateOut()
+ assertThat(dropTargetView).isNull()
+
+ runOnMainSync { controller.setDropTargetHidden(false) }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNull()
+ }
+
+ @Test
+ fun onDragEnd_dropTargetExists() {
+ runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNotNull()
+
+ runOnMainSync { controller.onDragEnd() }
+ waitForAnimateOut()
+ assertThat(dropTargetView).isNull()
+ }
+
+ @Test
+ fun onDragEnd_noDropTarget() {
+ runOnMainSync { controller.onDragEnd() }
+ waitForAnimateOut()
+ assertThat(dropTargetView).isNull()
+ }
+
+ private val dropTargetView: View?
+ get() = container.findViewById(R.id.bubble_bar_drop_target)
+
+ private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect {
+ val rect = Rect()
+ positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, rect)
+ // Scale the rect to expected size, but keep the center point the same
+ val centerX = rect.centerX()
+ val centerY = rect.centerY()
+ rect.scale(DROP_TARGET_SCALE)
+ rect.offset(centerX - rect.centerX(), centerY - rect.centerY())
+ return rect
+ }
+
+ private fun runOnMainSync(runnable: Runnable) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable)
+ }
+
+ private fun waitForAnimateIn() {
+ // Advance animator for on-device test
+ runOnMainSync { animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_IN_DURATION) }
+ }
+
+ private fun waitForAnimateOut() {
+ // Advance animator for on-device test
+ runOnMainSync { animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_OUT_DURATION) }
+ }
+
+ internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
+ val locationChanges = mutableListOf<BubbleBarLocation>()
+ override fun onChange(location: BubbleBarLocation) {
+ locationChanges.add(location)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetController.kt
deleted file mode 100644
index f6b4653b8162..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDropTargetController.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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.wm.shell.bubbles.bar
-
-import android.content.Context
-import android.graphics.Rect
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.FrameLayout
-import android.widget.FrameLayout.LayoutParams
-import androidx.annotation.VisibleForTesting
-import androidx.core.animation.Animator
-import androidx.core.animation.AnimatorListenerAdapter
-import androidx.core.animation.ObjectAnimator
-import com.android.wm.shell.R
-import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
-
-/** Controller to show/hide drop target when bubble bar expanded view is being dragged */
-class BubbleBarDropTargetController(
- val context: Context,
- val container: FrameLayout,
- val positioner: BubblePositioner
-) {
-
- private var dropTargetView: View? = null
- private var animator: ObjectAnimator? = null
- private val tempRect: Rect by lazy(LazyThreadSafetyMode.NONE) { Rect() }
-
- /**
- * Show drop target at [location] with animation.
- *
- * If the drop target is currently visible, animates it out first, before showing it at the
- * supplied location.
- */
- fun show(location: BubbleBarLocation) {
- val targetView = dropTargetView ?: createView().also { dropTargetView = it }
- if (targetView.alpha > 0) {
- targetView.animateOut {
- targetView.updateBounds(location)
- targetView.animateIn()
- }
- } else {
- targetView.updateBounds(location)
- targetView.animateIn()
- }
- }
-
- /**
- * Set the view hidden or not
- *
- * Requires the drop target to be first shown by calling [animateIn]. Otherwise does not do
- * anything.
- */
- fun setHidden(hidden: Boolean) {
- val targetView = dropTargetView ?: return
- if (hidden) {
- targetView.animateOut()
- } else {
- targetView.animateIn()
- }
- }
-
- /** Remove the drop target if it is was shown. */
- fun dismiss() {
- dropTargetView?.animateOut {
- dropTargetView?.let { container.removeView(it) }
- dropTargetView = null
- }
- }
-
- private fun createView(): View {
- return LayoutInflater.from(context)
- .inflate(R.layout.bubble_bar_drop_target, container, false /* attachToRoot */)
- .also { view: View ->
- view.alpha = 0f
- // Add at index 0 to ensure it does not cover the bubble
- container.addView(view, 0)
- }
- }
-
- private fun getBounds(onLeft: Boolean, out: Rect) {
- positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOverflowExpanded */, out)
- val centerX = out.centerX()
- val centerY = out.centerY()
- out.scale(DROP_TARGET_SCALE)
- // Move rect center back to the same position as before scale
- out.offset(centerX - out.centerX(), centerY - out.centerY())
- }
-
- private fun View.updateBounds(location: BubbleBarLocation) {
- getBounds(location.isOnLeft(isLayoutRtl), tempRect)
- val lp = layoutParams as LayoutParams
- lp.width = tempRect.width()
- lp.height = tempRect.height()
- layoutParams = lp
- x = tempRect.left.toFloat()
- y = tempRect.top.toFloat()
- }
-
- private fun View.animateIn() {
- animator?.cancel()
- animator =
- ObjectAnimator.ofFloat(this, View.ALPHA, 1f)
- .setDuration(DROP_TARGET_ALPHA_IN_DURATION)
- .addEndAction { animator = null }
- animator?.start()
- }
-
- private fun View.animateOut(endAction: Runnable? = null) {
- animator?.cancel()
- animator =
- ObjectAnimator.ofFloat(this, View.ALPHA, 0f)
- .setDuration(DROP_TARGET_ALPHA_OUT_DURATION)
- .addEndAction {
- endAction?.run()
- animator = null
- }
- animator?.start()
- }
-
- private fun <T : Animator> T.addEndAction(runnable: Runnable): T {
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- runnable.run()
- }
- }
- )
- return this
- }
-
- companion object {
- @VisibleForTesting const val DROP_TARGET_ALPHA_IN_DURATION = 150L
- @VisibleForTesting const val DROP_TARGET_ALPHA_OUT_DURATION = 100L
- @VisibleForTesting const val DROP_TARGET_SCALE = 0.9f
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index ad97a2411ae0..fe9c4d4c9094 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -17,12 +17,9 @@
package com.android.wm.shell.bubbles.bar
import android.annotation.SuppressLint
-import android.graphics.RectF
import android.view.MotionEvent
import android.view.View
-import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.common.bubbles.BubbleBarLocation
import com.android.wm.shell.common.bubbles.DismissView
import com.android.wm.shell.common.bubbles.RelativeTouchListener
import com.android.wm.shell.common.magnetictarget.MagnetizedObject
@@ -34,6 +31,7 @@ class BubbleBarExpandedViewDragController(
private val dismissView: DismissView,
private val animationHelper: BubbleBarAnimationHelper,
private val bubblePositioner: BubblePositioner,
+ private val pinController: BubbleExpandedViewPinController,
private val dragListener: DragListener
) {
@@ -45,8 +43,6 @@ class BubbleBarExpandedViewDragController(
private val magnetizedExpandedView: MagnetizedObject<BubbleBarExpandedView> =
MagnetizedObject.magnetizeView(expandedView)
private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget
- private val dismissZoneHeight: Int
- private val dismissZoneWidth: Int
init {
magnetizedExpandedView.magnetListener = MagnetListener()
@@ -78,33 +74,11 @@ class BubbleBarExpandedViewDragController(
}
return@setOnTouchListener dragMotionEventHandler.onTouch(view, event) || magnetConsumed
}
-
- dismissZoneHeight =
- dismissView.resources.getDimensionPixelSize(R.dimen.bubble_bar_dismiss_zone_height)
- dismissZoneWidth =
- dismissView.resources.getDimensionPixelSize(R.dimen.bubble_bar_dismiss_zone_width)
}
- /** Listener to receive callback about dragging events */
+ /** Listener to get notified about drag events */
interface DragListener {
/**
- * Bubble bar [BubbleBarLocation] has changed as a result of dragging the expanded view.
- *
- * Triggered when drag gesture passes the middle of the screen and before touch up. Can be
- * triggered multiple times per gesture.
- *
- * @param location new location of the bubble bar as a result of the ongoing drag operation
- */
- fun onLocationChanged(location: BubbleBarLocation)
-
- /**
- * Called when bubble bar is moved into or out of the dismiss target
- *
- * @param isStuck `true` if view is dragged inside dismiss target
- */
- fun onStuckToDismissChanged(isStuck: Boolean)
-
- /**
* Bubble bar was released
*
* @param inDismiss `true` if view was release in dismiss target
@@ -115,25 +89,11 @@ class BubbleBarExpandedViewDragController(
private inner class HandleDragListener : RelativeTouchListener() {
private var isMoving = false
- private var screenCenterX: Int = -1
- private var isOnLeft = false
- private val dismissZone = RectF()
override fun onDown(v: View, ev: MotionEvent): Boolean {
// While animating, don't allow new touch events
if (expandedView.isAnimating) return false
- isOnLeft = bubblePositioner.isBubbleBarOnLeft
-
- val screenRect = bubblePositioner.screenRect
- screenCenterX = screenRect.centerX()
- val screenBottom = screenRect.bottom
-
- dismissZone.set(
- screenCenterX - dismissZoneWidth / 2f,
- (screenBottom - dismissZoneHeight).toFloat(),
- screenCenterX + dismissZoneHeight / 2f,
- screenBottom.toFloat()
- )
+ pinController.onDragStart(bubblePositioner.isBubbleBarOnLeft)
return true
}
@@ -152,19 +112,7 @@ class BubbleBarExpandedViewDragController(
expandedView.translationX = expandedViewInitialTranslationX + dx
expandedView.translationY = expandedViewInitialTranslationY + dy
dismissView.show()
-
- // Check if we are in the zone around dismiss view where drag can only lead to dismiss
- if (dismissZone.contains(ev.rawX, ev.rawY)) {
- return
- }
-
- if (isOnLeft && ev.rawX > screenCenterX) {
- isOnLeft = false
- dragListener.onLocationChanged(BubbleBarLocation.RIGHT)
- } else if (!isOnLeft && ev.rawX < screenCenterX) {
- isOnLeft = true
- dragListener.onLocationChanged(BubbleBarLocation.LEFT)
- }
+ pinController.onDragUpdate(ev.rawX, ev.rawY)
}
override fun onUp(
@@ -188,6 +136,7 @@ class BubbleBarExpandedViewDragController(
private fun finishDrag() {
if (!isStuckToDismiss) {
animationHelper.animateToRestPosition()
+ pinController.onDragEnd()
dragListener.onReleased(inDismiss = false)
dismissView.hide()
}
@@ -201,7 +150,7 @@ class BubbleBarExpandedViewDragController(
draggedObject: MagnetizedObject<*>
) {
isStuckToDismiss = true
- dragListener.onStuckToDismissChanged(isStuck = true)
+ pinController.setDropTargetHidden(true)
}
override fun onUnstuckFromTarget(
@@ -213,7 +162,7 @@ class BubbleBarExpandedViewDragController(
) {
isStuckToDismiss = false
animationHelper.animateUnstuckFromDismissView(target)
- dragListener.onStuckToDismissChanged(isStuck = false)
+ pinController.setDropTargetHidden(false)
}
override fun onReleasedInTarget(
@@ -221,6 +170,7 @@ class BubbleBarExpandedViewDragController(
draggedObject: MagnetizedObject<*>
) {
dragListener.onReleased(inDismiss = true)
+ pinController.onDragEnd()
dismissView.hide()
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 88ccc9267c41..62cc4da3193e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -33,8 +33,6 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;
-import androidx.annotation.NonNull;
-
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
@@ -44,7 +42,6 @@ import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.DeviceConfig;
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
-import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.DismissView;
import kotlin.Unit;
@@ -71,7 +68,7 @@ public class BubbleBarLayerView extends FrameLayout
private final BubbleBarAnimationHelper mAnimationHelper;
private final BubbleEducationViewController mEducationViewController;
private final View mScrimView;
- private final BubbleBarDropTargetController mDropTargetController;
+ private final BubbleExpandedViewPinController mBubbleExpandedViewPinController;
@Nullable
private BubbleViewProvider mExpandedBubble;
@@ -116,7 +113,9 @@ public class BubbleBarLayerView extends FrameLayout
setUpDismissView();
- mDropTargetController = new BubbleBarDropTargetController(context, this, mPositioner);
+ mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
+ context, this, mPositioner);
+ mBubbleExpandedViewPinController.setListener(mBubbleController::setBubbleBarLocation);
setOnClickListener(view -> hideMenuOrCollapse());
}
@@ -207,12 +206,17 @@ public class BubbleBarLayerView extends FrameLayout
}
});
- DragListener dragListener = createDragListener();
+ DragListener dragListener = inDismiss -> {
+ if (inDismiss && mExpandedBubble != null) {
+ mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE);
+ }
+ };
mDragController = new BubbleBarExpandedViewDragController(
mExpandedView,
mDismissView,
mAnimationHelper,
mPositioner,
+ mBubbleExpandedViewPinController,
dragListener);
addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
@@ -377,26 +381,4 @@ public class BubbleBarLayerView extends FrameLayout
}
}
- private DragListener createDragListener() {
- return new DragListener() {
- @Override
- public void onLocationChanged(@NonNull BubbleBarLocation location) {
- mBubbleController.setBubbleBarLocation(location);
- mDropTargetController.show(location);
- }
-
- @Override
- public void onStuckToDismissChanged(boolean isStuck) {
- mDropTargetController.setHidden(isStuck);
- }
-
- @Override
- public void onReleased(boolean inDismiss) {
- mDropTargetController.dismiss();
- if (inDismiss && mExpandedBubble != null) {
- mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE);
- }
- }
- };
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
new file mode 100644
index 000000000000..5d391eca070c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.wm.shell.bubbles.bar
+
+import android.content.Context
+import android.graphics.Rect
+import android.graphics.RectF
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.annotation.VisibleForTesting
+import androidx.core.view.updateLayoutParams
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.BubblePositioner
+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
+ * expanded view
+ */
+class BubbleExpandedViewPinController(
+ private val context: Context,
+ private val container: FrameLayout,
+ private val positioner: BubblePositioner
+) : BaseBubblePinController() {
+
+ private var dropTargetView: View? = null
+ private val tempRect: Rect by lazy(LazyThreadSafetyMode.NONE) { Rect() }
+
+ override fun getScreenCenterX(): Int {
+ return positioner.screenRect.centerX()
+ }
+
+ override fun getExclusionRect(): RectF {
+ val rect =
+ RectF(
+ 0f,
+ 0f,
+ context.resources.getDimension(R.dimen.bubble_bar_dismiss_zone_width),
+ context.resources.getDimension(R.dimen.bubble_bar_dismiss_zone_height)
+ )
+
+ val screenRect = positioner.screenRect
+ // Center it around the bottom center of the screen
+ rect.offsetTo(
+ screenRect.exactCenterX() - rect.width() / 2f,
+ screenRect.bottom - rect.height()
+ )
+ return rect
+ }
+
+ override fun createDropTargetView(): View {
+ return LayoutInflater.from(context)
+ .inflate(R.layout.bubble_bar_drop_target, container, false /* attachToRoot */)
+ .also { view: View ->
+ dropTargetView = view
+ // Add at index 0 to ensure it does not cover the bubble
+ container.addView(view, 0)
+ }
+ }
+
+ override fun getDropTargetView(): View? {
+ return dropTargetView
+ }
+
+ override fun removeDropTargetView(view: View) {
+ container.removeView(view)
+ dropTargetView = null
+ }
+
+ override fun updateLocation(location: BubbleBarLocation) {
+ val view = dropTargetView ?: return
+ getBounds(location.isOnLeft(view.isLayoutRtl), tempRect)
+ view.updateLayoutParams<FrameLayout.LayoutParams> {
+ width = tempRect.width()
+ height = tempRect.height()
+ }
+ view.x = tempRect.left.toFloat()
+ view.y = tempRect.top.toFloat()
+ }
+
+ private fun getBounds(onLeft: Boolean, out: Rect) {
+ positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOverflowExpanded */, out)
+ val centerX = out.centerX()
+ val centerY = out.centerY()
+ out.scale(DROP_TARGET_SCALE)
+ // Move rect center back to the same position as before scale
+ out.offset(centerX - out.centerX(), centerY - out.centerY())
+ }
+
+ companion object {
+ @VisibleForTesting const val DROP_TARGET_SCALE = 0.9f
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
new file mode 100644
index 000000000000..630ad6e7cafe
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
@@ -0,0 +1,179 @@
+/*
+ * 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.wm.shell.common.bubbles
+
+import android.graphics.RectF
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.ObjectAnimator
+import com.android.wm.shell.common.bubbles.BaseBubblePinController.LocationChangeListener
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
+
+/**
+ * Base class for common logic shared between different bubble views to support pinning bubble bar
+ * to left or right edge of screen.
+ *
+ * Handles drag events and allows a [LocationChangeListener] to be registered that is notified when
+ * location of the bubble bar should change.
+ *
+ * Shows a drop target when releasing a view would update the [BubbleBarLocation].
+ */
+abstract class BaseBubblePinController {
+
+ private var onLeft = false
+ private var dismissZone: RectF? = null
+ private var screenCenterX = 0
+ private var listener: LocationChangeListener? = null
+ private var dropTargetAnimator: ObjectAnimator? = null
+
+ /**
+ * Signal the controller that dragging interaction has started.
+ *
+ * @param initialLocationOnLeft side of the screen where bubble bar is pinned to
+ */
+ fun onDragStart(initialLocationOnLeft: Boolean) {
+ onLeft = initialLocationOnLeft
+ dismissZone = getExclusionRect()
+ screenCenterX = getScreenCenterX()
+ }
+
+ /** View has moved to [x] and [y] screen coordinates */
+ fun onDragUpdate(x: Float, y: Float) {
+ if (dismissZone?.contains(x, y) == true) return
+
+ if (onLeft && x > screenCenterX) {
+ onLeft = false
+ onLocationChange(RIGHT)
+ } else if (!onLeft && x < screenCenterX) {
+ onLeft = true
+ onLocationChange(LEFT)
+ }
+ }
+
+ /** Temporarily hide the drop target view */
+ fun setDropTargetHidden(hidden: Boolean) {
+ val targetView = getDropTargetView() ?: return
+ if (hidden) {
+ targetView.animateOut()
+ } else {
+ targetView.animateIn()
+ }
+ }
+
+ /** Signal the controller that dragging interaction has finished. */
+ fun onDragEnd() {
+ getDropTargetView()?.let { view -> view.animateOut { removeDropTargetView(view) } }
+ dismissZone = null
+ }
+
+ /**
+ * [LocationChangeListener] that is notified when dragging interaction has resulted in bubble
+ * bar to be pinned on the other edge
+ */
+ fun setListener(listener: LocationChangeListener?) {
+ this.listener = listener
+ }
+
+ /** Get screen center coordinate on the x axis. */
+ protected abstract fun getScreenCenterX(): Int
+
+ /** Optional exclusion rect where drag interactions are not processed */
+ protected abstract fun getExclusionRect(): RectF?
+
+ /** Create the drop target view and attach it to the parent */
+ protected abstract fun createDropTargetView(): View
+
+ /** Get the drop target view if it exists */
+ protected abstract fun getDropTargetView(): View?
+
+ /** Remove the drop target view */
+ protected abstract fun removeDropTargetView(view: View)
+
+ /** Update size and location of the drop target view */
+ protected abstract fun updateLocation(location: BubbleBarLocation)
+
+ private fun onLocationChange(location: BubbleBarLocation) {
+ showDropTarget(location)
+ listener?.onChange(location)
+ }
+
+ private fun showDropTarget(location: BubbleBarLocation) {
+ val targetView = getDropTargetView() ?: createDropTargetView().apply { alpha = 0f }
+ if (targetView.alpha > 0) {
+ targetView.animateOut {
+ updateLocation(location)
+ targetView.animateIn()
+ }
+ } else {
+ updateLocation(location)
+ targetView.animateIn()
+ }
+ }
+
+ private fun View.animateIn() {
+ dropTargetAnimator?.cancel()
+ dropTargetAnimator =
+ ObjectAnimator.ofFloat(this, View.ALPHA, 1f)
+ .setDuration(DROP_TARGET_ALPHA_IN_DURATION)
+ .addEndAction { dropTargetAnimator = null }
+ dropTargetAnimator?.start()
+ }
+
+ private fun View.animateOut(endAction: Runnable? = null) {
+ dropTargetAnimator?.cancel()
+ dropTargetAnimator =
+ ObjectAnimator.ofFloat(this, View.ALPHA, 0f)
+ .setDuration(DROP_TARGET_ALPHA_OUT_DURATION)
+ .addEndAction {
+ endAction?.run()
+ dropTargetAnimator = null
+ }
+ dropTargetAnimator?.start()
+ }
+
+ private fun <T : Animator> T.addEndAction(runnable: Runnable): T {
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ runnable.run()
+ }
+ }
+ )
+ return this
+ }
+
+ /** Receive updates on location changes */
+ interface LocationChangeListener {
+ /**
+ * Bubble bar [BubbleBarLocation] has changed as a result of dragging
+ *
+ * Triggered when drag gesture passes the middle of the screen and before touch up. Can be
+ * triggered multiple times per gesture.
+ *
+ * @param location new location as a result of the ongoing drag operation
+ */
+ fun onChange(location: BubbleBarLocation)
+ }
+
+ companion object {
+ @VisibleForTesting const val DROP_TARGET_ALPHA_IN_DURATION = 150L
+ @VisibleForTesting const val DROP_TARGET_ALPHA_OUT_DURATION = 100L
+ }
+}