Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1bb44936 authored by Ats Jenk's avatar Ats Jenk Committed by Android (Google) Code Review
Browse files

Merge "Update dismiss view location in one-handed mode" into main

parents 60fdaef6 7866fca3
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -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();
    }

    /**
+39 −7
Original line number Diff line number Diff line
@@ -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
        }

@@ -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
@@ -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) {
@@ -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. */
@@ -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() {
@@ -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 {
+70 −6
Original line number Diff line number Diff line
@@ -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`
@@ -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,
@@ -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