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

Commit b30445a4 authored by Yongshun Liu's avatar Yongshun Liu
Browse files

a11y: Throttle motion events for magnification UI update

This change introduces a throttling mechanism for motion events
to prevent excessive UI updates in magnification.

Previously, only mouse events were throttled, leading to potential
performance issues with touch events. This change extends the
throttling to touch events as well, guarded by a feature flag
`throttle_motion_events_for_ui_update`.

The following tests have been added to ensure the throttling works
as expected with and without the feature flag:
- onTouchEvent_withThrottleEnabled_shouldRateLimit
- onTouchEvent_withThrottleEnabled_shouldNotRateLimitAfterDelay
- onTouchEvent_withThrottleDisabled_shouldNotRateLimit

Bug: 435498747
Flag: com.android.server.accessibility.throttle_motion_events_for_ui_update
Test: atest MagnificationControllerTest
Change-Id: I2283d944ab30c6eed4f03d56d42afac8c6ec33c2
parent 0b4e1d2f
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -158,6 +158,16 @@ flag {
    bug: "322829049"
}

flag {
    name: "throttle_motion_events_for_ui_update"
    namespace: "accessibility"
    description: "Throttles motion events for requesting the System UI to update magnification UIs."
    bug: "435498747"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "touch_explorer_a11y_events_include_display_id"
    namespace: "accessibility"
+17 −13
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.Flags;
import com.android.server.wm.WindowManagerInternal;

import java.util.concurrent.Executor;
@@ -126,7 +127,7 @@ public class MagnificationController implements MagnificationConnectionManager.C
    // in multiple directions at once (for example, up + left), tracking last
    // panned time ensures that panning doesn't occur too frequently.
    private long mLastPannedTime = 0;
    private long mLastMouseMoveTriggeredUiChangeTime = 0;
    private long mLastMotionEventTriggeredUiChangeTime = 0;
    private boolean mRepeatKeysEnabled = true;

    private @ZoomDirection int mActiveZoomDirection = ZOOM_DIRECTION_IN;
@@ -388,25 +389,17 @@ public class MagnificationController implements MagnificationConnectionManager.C

    @Override
    public void onTouchInteractionStart(int displayId, int mode) {
        // TODO(435498747): Add throttling for touch similarly to mouse events.
        handleUserInteractionChanged(displayId, mode);
        handleUserInteractionChanged(displayId, mode, /* isMouse= */ false);
    }

    @Override
    public void onTouchInteractionEnd(int displayId, int mode) {
        // TODO(435498747): Add throttling for touch similarly to mouse events.
        handleUserInteractionChanged(displayId, mode);
        handleUserInteractionChanged(displayId, mode, /* isMouse= */ false);
    }

    @Override
    public void onMouseMove(int displayId, int mode) {
        final long currentTime = mSystemClock.uptimeMillis();
        if (currentTime - mLastMouseMoveTriggeredUiChangeTime
                < AccessibilityUtils.MAGNIFICATION_HANDLE_UI_CHANGE_INTERVAL_MS) {
            return;
        }
        mLastMouseMoveTriggeredUiChangeTime = currentTime;
        handleUserInteractionChanged(displayId, mode);
        handleUserInteractionChanged(displayId, mode, /* isMouse= */ true);
    }

    @Override
@@ -524,10 +517,21 @@ public class MagnificationController implements MagnificationConnectionManager.C
        return mInitialKeyboardRepeatIntervalMs;
    }

    private void handleUserInteractionChanged(int displayId, int mode) {
    private void handleUserInteractionChanged(int displayId, int mode, boolean isMouse) {
        if (mMagnificationCapabilities != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
            return;
        }

        // Mouse events are always throttled. Touch events are throttled only when the flag is on.
        if (isMouse || Flags.throttleMotionEventsForUiUpdate()) {
            final long currentTime = mSystemClock.uptimeMillis();
            if (currentTime - mLastMotionEventTriggeredUiChangeTime
                    < AccessibilityUtils.MAGNIFICATION_HANDLE_UI_CHANGE_INTERVAL_MS) {
                return;
            }
            mLastMotionEventTriggeredUiChangeTime = currentTime;
        }

        updateMagnificationUIControls(displayId, mode);
    }

+62 −0
Original line number Diff line number Diff line
@@ -2234,6 +2234,68 @@ public class MagnificationControllerTest {
                TEST_DISPLAY, MODE_FULLSCREEN);
    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_THROTTLE_MOTION_EVENTS_FOR_UI_UPDATE)
    public void onTouchEvent_withThrottleEnabled_shouldRateLimit() throws RemoteException {
        mMagnificationController.setMagnificationCapabilities(
                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
        setMagnificationEnabled(MODE_FULLSCREEN);
        clearInvocations(mMagnificationConnectionManager);

        // The first call should go through and trigger a UI update.
        mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_FULLSCREEN);
        verify(mMagnificationConnectionManager).showMagnificationButton(
                TEST_DISPLAY, MODE_FULLSCREEN);
        clearInvocations(mMagnificationConnectionManager);

        // Subsequent call within the throttle period should be ignored.
        mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_FULLSCREEN);
        verify(mMagnificationConnectionManager, never()).showMagnificationButton(
                TEST_DISPLAY, MODE_FULLSCREEN);
    }

    @Test
    @EnableFlags(com.android.server.accessibility.Flags.FLAG_THROTTLE_MOTION_EVENTS_FOR_UI_UPDATE)
    public void onTouchEvent_withThrottleEnabled_shouldNotRateLimitAfterDelay()
            throws RemoteException {
        mMagnificationController.setMagnificationCapabilities(
                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
        setMagnificationEnabled(MODE_FULLSCREEN);
        clearInvocations(mMagnificationConnectionManager);

        // The first call should go through and trigger a UI update.
        mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_FULLSCREEN);
        verify(mMagnificationConnectionManager).showMagnificationButton(
                TEST_DISPLAY, MODE_FULLSCREEN);
        clearInvocations(mMagnificationConnectionManager);

        // Advance time past the throttle period. The next call should now go through.
        mSystemClock.advanceTime(AccessibilityUtils.MAGNIFICATION_HANDLE_UI_CHANGE_INTERVAL_MS + 1);
        mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_FULLSCREEN);
        verify(mMagnificationConnectionManager).showMagnificationButton(
                TEST_DISPLAY, MODE_FULLSCREEN);
    }

    @Test
    @DisableFlags(com.android.server.accessibility.Flags.FLAG_THROTTLE_MOTION_EVENTS_FOR_UI_UPDATE)
    public void onTouchEvent_withThrottleDisabled_shouldNotRateLimit() throws RemoteException {
        mMagnificationController.setMagnificationCapabilities(
                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
        setMagnificationEnabled(MODE_FULLSCREEN);
        clearInvocations(mMagnificationConnectionManager);

        // The first call should go through and trigger a UI update.
        mMagnificationController.onTouchInteractionStart(TEST_DISPLAY, MODE_FULLSCREEN);
        verify(mMagnificationConnectionManager).showMagnificationButton(
                TEST_DISPLAY, MODE_FULLSCREEN);
        clearInvocations(mMagnificationConnectionManager);

        // Subsequent call within the throttle period should also go through.
        mMagnificationController.onTouchInteractionEnd(TEST_DISPLAY, MODE_FULLSCREEN);
        verify(mMagnificationConnectionManager).showMagnificationButton(
                TEST_DISPLAY, MODE_FULLSCREEN);
    }

    @Test
    public void enableWindowMode_showMagnificationButton()
            throws RemoteException {