Merge "Update dismiss view location in one-handed mode" into main
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c5bc9eb..35c1e8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -3521,7 +3521,14 @@
*/
void onVerticalOffsetChanged(int offset) {
// adjust dismiss view vertical position, so that it is still visible to the user
- mDismissView.setPadding(/* left = */ 0, /* top = */ 0, /* right = */ 0, offset);
+ ViewGroup.LayoutParams lp = mDismissView.getLayoutParams();
+ if (lp instanceof FrameLayout.LayoutParams) {
+ FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) lp;
+ layoutParams.bottomMargin = offset;
+ mDismissView.setLayoutParams(layoutParams);
+ }
+ mMagneticTarget.setScreenVerticalOffset(offset);
+ mMagneticTarget.updateLocationOnScreen();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index aac1d062..7c931df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -352,8 +352,8 @@
val targetObjectIsInMagneticFieldOf = associatedTargets.firstOrNull { target ->
val distanceFromTargetCenter = hypot(
- ev.rawX - target.centerOnScreen.x,
- ev.rawY - target.centerOnScreen.y)
+ ev.rawX - target.centerOnDisplayX(),
+ ev.rawY - target.centerOnDisplayY())
distanceFromTargetCenter < target.magneticFieldRadiusPx
}
@@ -406,7 +406,6 @@
// First, check for relevant gestures concluding with an ACTION_UP.
if (ev.action == MotionEvent.ACTION_UP) {
-
velocityTracker.computeCurrentVelocity(1000 /* units */)
val velX = velocityTracker.xVelocity
val velY = velocityTracker.yVelocity
@@ -542,7 +541,7 @@
// Whether velocity is sufficient, depending on whether we're flinging into a target at the
// top or the bottom of the screen.
val velocitySufficient =
- if (rawY < target.centerOnScreen.y) velY > flingToTargetMinVelocity
+ if (rawY < target.centerOnDisplayY()) velY > flingToTargetMinVelocity
else velY < flingToTargetMinVelocity
if (!velocitySufficient) {
@@ -560,15 +559,15 @@
val yIntercept = rawY - slope * rawX
// ...calculate the x value when y = the target's y-coordinate.
- targetCenterXIntercept = (target.centerOnScreen.y - yIntercept) / slope
+ targetCenterXIntercept = (target.centerOnDisplayY() - yIntercept) / slope
}
// The width of the area we're looking for a fling towards.
val targetAreaWidth = target.targetView.width * flingToTargetWidthPercent
// Velocity was sufficient, so return true if the intercept is within the target area.
- return targetCenterXIntercept > target.centerOnScreen.x - targetAreaWidth / 2 &&
- targetCenterXIntercept < target.centerOnScreen.x + targetAreaWidth / 2
+ return targetCenterXIntercept > target.centerOnDisplayX() - targetAreaWidth / 2 &&
+ targetCenterXIntercept < target.centerOnDisplayX() + targetAreaWidth / 2
}
/** Cancel animations on this object's x/y properties. */
@@ -601,6 +600,22 @@
) {
val centerOnScreen = PointF()
+ /**
+ * Set screen vertical offset amount.
+ *
+ * Screen surface may be vertically shifted in some cases, for example when one-handed mode
+ * is enabled. [MagneticTarget] and [MagnetizedObject] set their location in screen
+ * coordinates (see [MagneticTarget.centerOnScreen] and
+ * [MagnetizedObject.getLocationOnScreen] respectively).
+ *
+ * When a [MagnetizedObject] is dragged, the touch location is determined by
+ * [MotionEvent.getRawX] and [MotionEvent.getRawY]. These work in display coordinates. When
+ * screen is shifted due to one-handed mode, display coordinates and screen coordinates do
+ * not match. To determine if a [MagnetizedObject] is dragged into a [MagneticTarget], view
+ * location on screen is translated to display coordinates using this offset value.
+ */
+ var screenVerticalOffset: Int = 0
+
private val tempLoc = IntArray(2)
fun updateLocationOnScreen() {
@@ -614,6 +629,23 @@
tempLoc[1] + targetView.height / 2f - targetView.translationY)
}
}
+
+ /**
+ * Get target center coordinate on x-axis on display. [centerOnScreen] has to be up to date
+ * by calling [updateLocationOnScreen] first.
+ */
+ fun centerOnDisplayX(): Float {
+ return centerOnScreen.x
+ }
+
+ /**
+ * Get target center coordinate on y-axis on display. [centerOnScreen] has to be up to date
+ * by calling [updateLocationOnScreen] first. Use [screenVerticalOffset] to update the
+ * screen offset compared to the display.
+ */
+ fun centerOnDisplayY(): Float {
+ return centerOnScreen.y + screenVerticalOffset
+ }
}
companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
index 9f1ee6c..a9f054e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
@@ -30,6 +30,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.Mockito
import org.mockito.Mockito.`when`
@@ -97,7 +98,7 @@
// The mock target view will pretend that it's 200x200, and at (400, 800). This means it's
// occupying the bounds (400, 800, 600, 1000) and it has a center of (500, 900).
- `when`(targetView.width).thenReturn(targetSize) // width = 200
+ `when`(targetView.width).thenReturn(targetSize) // width = 200
`when`(targetView.height).thenReturn(targetSize) // height = 200
doAnswer { invocation ->
(invocation.arguments[0] as IntArray).also { location ->
@@ -275,11 +276,11 @@
// Forcefully fling the object towards the target (but never touch the magnetic field).
dispatchMotionEvents(
getMotionEvent(
- x = targetCenterX,
+ x = 0,
y = 0,
action = MotionEvent.ACTION_DOWN),
getMotionEvent(
- x = targetCenterX,
+ x = targetCenterX / 2,
y = targetCenterY / 2),
getMotionEvent(
x = targetCenterX,
@@ -405,15 +406,78 @@
verify(magnetListener).onStuckToTarget(magneticTarget)
}
+ @Test
+ fun testMagneticTargetHasScreenOffset_moveIntoAndReleaseInTarget() {
+ magneticTarget.screenVerticalOffset = 500
+
+ dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = targetCenterY))
+ // Moved into the target location, but it should be shifted due to screen offset.
+ // Should not get stuck.
+ verify(magnetListener, never()).onStuckToTarget(magneticTarget)
+
+ dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = targetCenterY + 500))
+ verify(magnetListener).onStuckToTarget(magneticTarget)
+
+ dispatchMotionEvents(
+ getMotionEvent(
+ x = targetCenterX,
+ y = targetCenterY + 500,
+ action = MotionEvent.ACTION_UP
+ )
+ )
+
+ verify(magnetListener).onReleasedInTarget(magneticTarget)
+ verifyNoMoreInteractions(magnetListener)
+ }
+
+ @Test
+ fun testMagneticTargetHasScreenOffset_screenOffsetUpdates() {
+ magneticTarget.screenVerticalOffset = 500
+ val adjustedTargetCenter = targetCenterY + 500
+
+ dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = adjustedTargetCenter))
+ dispatchMotionEvents(getMotionEvent(x = 0, y = 0))
+ verify(magnetListener).onStuckToTarget(magneticTarget)
+ verify(magnetListener)
+ .onUnstuckFromTarget(eq(magneticTarget), anyFloat(), anyFloat(), anyBoolean())
+
+ // Offset if removed, we should now get stuck at the target location
+ magneticTarget.screenVerticalOffset = 0
+ dispatchMotionEvents(getMotionEvent(x = targetCenterX, y = targetCenterY))
+ verify(magnetListener, times(2)).onStuckToTarget(magneticTarget)
+ }
+
+ @Test
+ fun testMagneticTargetHasScreenOffset_flingTowardsTarget() {
+ timeStep = 10
+
+ magneticTarget.screenVerticalOffset = 500
+ val adjustedTargetCenter = targetCenterY + 500
+
+ // Forcefully fling the object towards the target (but never touch the magnetic field).
+ dispatchMotionEvents(
+ getMotionEvent(x = 0, y = 0, action = MotionEvent.ACTION_DOWN),
+ getMotionEvent(x = targetCenterX / 2, y = adjustedTargetCenter / 2),
+ getMotionEvent(
+ x = targetCenterX,
+ y = adjustedTargetCenter - magneticFieldRadius * 2,
+ action = MotionEvent.ACTION_UP
+ )
+ )
+
+ // Nevertheless it should have ended up stuck to the target.
+ verify(magnetListener, times(1)).onStuckToTarget(magneticTarget)
+ }
+
private fun getSecondMagneticTarget(): MagnetizedObject.MagneticTarget {
// The first target view is at bounds (400, 800, 600, 1000) and it has a center of
// (500, 900). We'll add a second one at bounds (0, 800, 200, 1000) with center (100, 900).
val secondTargetView = mock(View::class.java)
- var secondTargetCenterX = 100
- var secondTargetCenterY = 900
+ val secondTargetCenterX = 100
+ val secondTargetCenterY = 900
`when`(secondTargetView.context).thenReturn(context)
- `when`(secondTargetView.width).thenReturn(targetSize) // width = 200
+ `when`(secondTargetView.width).thenReturn(targetSize) // width = 200
`when`(secondTargetView.height).thenReturn(targetSize) // height = 200
doAnswer { invocation ->
(invocation.arguments[0] as Runnable).run()