Loading libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +8 −1 Original line number Diff line number Diff line Loading @@ -3521,7 +3521,14 @@ public class BubbleStackView extends FrameLayout */ 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(); } /** Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt +39 −7 Original line number Diff line number Diff line Loading @@ -352,8 +352,8 @@ abstract class MagnetizedObject<T : Any>( 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 } Loading Loading @@ -406,7 +406,6 @@ abstract class MagnetizedObject<T : Any>( // 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 Loading Loading @@ -542,7 +541,7 @@ abstract class MagnetizedObject<T : Any>( // 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) { Loading @@ -560,15 +559,15 @@ abstract class MagnetizedObject<T : Any>( 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. */ Loading Loading @@ -601,6 +600,22 @@ abstract class MagnetizedObject<T : Any>( ) { 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() { Loading @@ -614,6 +629,23 @@ abstract class MagnetizedObject<T : Any>( 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 { Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt +70 −6 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import org.junit.Before 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` Loading Loading @@ -275,11 +276,11 @@ class MagnetizedObjectTest : ShellTestCase() { // 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, Loading Loading @@ -405,12 +406,75 @@ class MagnetizedObjectTest : ShellTestCase() { 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 Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +8 −1 Original line number Diff line number Diff line Loading @@ -3521,7 +3521,14 @@ public class BubbleStackView extends FrameLayout */ 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(); } /** Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt +39 −7 Original line number Diff line number Diff line Loading @@ -352,8 +352,8 @@ abstract class MagnetizedObject<T : Any>( 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 } Loading Loading @@ -406,7 +406,6 @@ abstract class MagnetizedObject<T : Any>( // 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 Loading Loading @@ -542,7 +541,7 @@ abstract class MagnetizedObject<T : Any>( // 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) { Loading @@ -560,15 +559,15 @@ abstract class MagnetizedObject<T : Any>( 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. */ Loading Loading @@ -601,6 +600,22 @@ abstract class MagnetizedObject<T : Any>( ) { 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() { Loading @@ -614,6 +629,23 @@ abstract class MagnetizedObject<T : Any>( 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 { Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt +70 −6 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import org.junit.Before 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` Loading Loading @@ -275,11 +276,11 @@ class MagnetizedObjectTest : ShellTestCase() { // 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, Loading Loading @@ -405,12 +406,75 @@ class MagnetizedObjectTest : ShellTestCase() { 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 Loading