Android: Request unbuffered dispatch of touch and joystick input

Android batches touch moves and joystick axis events until the next UI
vsync, up to a frame of latency. On API 30+, ask for them unbatched.
Non-pointer sources only apply to the focused view, so the surface view
now stays focused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-02 08:52:24 +01:00
1 parent 5ea0921301
commit 1d477fa248
1 file changed
+23 -3
@@ -7,8 +7,10 @@ package org.ppsspp.ppsspp;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
import android.text.InputType;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceView;
@@ -28,6 +30,21 @@ public class NativeSurfaceView extends SurfaceView {
super(context);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Without this, touch moves and joystick axis events are batched until the next UI vsync,
// adding up to a frame of latency before the emulator sees them. Non-pointer sources only
// apply while this view is focused, so keep it focusable.
setFocusable(true);
setFocusableInTouchMode(true);
setDefaultFocusHighlightEnabled(false);
requestFocus();
requestUnbufferedDispatch(InputDevice.SOURCE_CLASS_POINTER | InputDevice.SOURCE_CLASS_JOYSTICK);
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(final MotionEvent ev) {
@@ -55,9 +72,12 @@ public class NativeSurfaceView extends SurfaceView {
} else {
imm.hideSoftInputFromWindow(getWindowToken(), 0);
imm.restartInput(this);
clearFocus();
setFocusableInTouchMode(false);
setFocusable(false);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
// On R+ we stay focused for unbuffered dispatch, see onAttachedToWindow.
clearFocus();
setFocusableInTouchMode(false);
setFocusable(false);
}
}
}