From 2123e7fdacd6a0d13fb7d346c1940755d040082e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 10 Oct 2026 15:45:27 +0200 Subject: [PATCH] Android: Tie emulation and audio to visibility, not focus onPause used to join the render thread and stop the audio, so anything taking focus while the game stayed on screen (a permission dialog, a share sheet, the other app of a split screen before Android 10) tore down the whole graphics context and silenced the game. Emulation, audio, sensors and input listeners now follow onStart/onStop. The render loop itself already follows the surface; onStop joins it too, for devices that keep a stopped activity's surface. The camera stays on onPause/onResume, as it goes to the focused app. A finishing activity lets go of the render thread in onPause, since a quick relaunch creates the next activity before this one's onStop, and that onStop then leaves the audio and location updates alone. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../src/org/ppsspp/ppsspp/PpssppActivity.java | 90 ++++++++++++------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index fa9f3a9859..73b27323ba 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -989,16 +989,64 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList Log.i(TAG, "onDestroy end"); } + // Emulation, audio and input follow visibility (onStart/onStop) rather than focus (onPause/onResume): a + // permission dialog on top, or the other app of a split screen having focus, leaves the game on screen, + // and it should keep running and playing. The render loop is really owned by the surface (notifySurface), + // which normally goes away with the visibility anyway. @Override protected void onStart() { super.onStart(); - Log.i(TAG, "onStart"); + if (m_hasNoNativeBinary) { + return; + } + Log.i(TAG, "onStart begin"); + + if (mLocationHelper != null) { + mLocationHelper.resume(); + } + + updateAudioFocus(this.audioManager, this.audioFocusChangeListener); + NativeApp.resume(); + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); + + InputManager inputManager = (InputManager)getSystemService(Context.INPUT_SERVICE); + inputManager.registerInputDeviceListener(inputDeviceListener, null); + + // In case the surface outlived the stop. Otherwise this waits for surfaceCreated. + startRenderLoopThread(); + Log.i(TAG, "onStart end"); } @Override protected void onStop() { super.onStop(); - Log.i(TAG, "onStop"); + if (m_hasNoNativeBinary) { + return; + } + Log.i(TAG, "onStop begin"); + + InputManager inputManager = (InputManager)getSystemService(Context.INPUT_SERVICE); + inputManager.unregisterInputDeviceListener(inputDeviceListener); + + // Normally surfaceDestroyed has done this already, but not every device destroys the surface of a + // stopped activity, and an invisible game shouldn't keep running. + Log.i(TAG, "Joining render thread..."); + joinRenderLoopThread(); + Log.i(TAG, "Joined render thread"); + + mSensorManager.unregisterListener(this); + loseAudioFocus(this.audioManager, this.audioFocusChangeListener); + + // After a finish() and a quick relaunch, this comes after the new activity's onStart, and the audio and + // the location updates are its now. + if (instance == latestInstance) { + Log.i(TAG, "Calling NativeApp.pause..."); + NativeApp.pause(); + if (mLocationHelper != null) { + mLocationHelper.pause(); + } + } + Log.i(TAG, "onStop end"); } @Override @@ -1008,27 +1056,17 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList return; } Log.i(TAG, "onPause begin"); - - InputManager inputManager = (InputManager)getSystemService(Context.INPUT_SERVICE); - inputManager.unregisterInputDeviceListener(inputDeviceListener); - - Log.i(TAG, "Joining render thread..."); - joinRenderLoopThread(); - Log.i(TAG, "Joined render thread"); - - Log.i(TAG, "mSensorManager.unregisterListener..."); - mSensorManager.unregisterListener(this); - - loseAudioFocus(this.audioManager, this.audioFocusChangeListener); - Log.i(TAG, "Calling NativeApp.pause..."); - NativeApp.pause(); + if (isFinishing()) { + // Gone for good. A quick relaunch creates the next activity before this one's onStop, and the + // render thread has to be free for it by then. + Log.i(TAG, "Finishing, joining render thread..."); + joinRenderLoopThread(); + } + // The camera goes to whichever app has focus. if (mCameraHelper != null) { Log.i(TAG, "Calling mCameraHelper.pause"); mCameraHelper.pause(); } - if (mLocationHelper != null) { - mLocationHelper.pause(); - } Log.i(TAG, "onPause end"); } @@ -1049,20 +1087,6 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList if (mCameraHelper != null) { mCameraHelper.resume(); } - if (mLocationHelper != null) { - mLocationHelper.resume(); - } - - updateAudioFocus(this.audioManager, this.audioFocusChangeListener); - NativeApp.resume(); - mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); - - InputManager inputManager = - (InputManager)getSystemService(Context.INPUT_SERVICE); - inputManager.registerInputDeviceListener(inputDeviceListener, null); - - // Restart the render loop. - startRenderLoopThread(); Log.i(TAG, "onResume end"); }