From 860acb4faf0b453912c451238b44f77e6a7a7fdf Mon Sep 17 00:00:00 2001 From: coolone Date: Wed, 22 Apr 2026 05:32:52 +0200 Subject: [PATCH] [android] Fix crash on start any games for many handhelds (Ayaneo, Retroid etc) (#3647) ## Summary This change hardens Android Picture-in-Picture handling to avoid runtime crashes on devices/ROMs where PiP APIs are unavailable or behave inconsistently. ## What changed - Added a feature gate for PiP support using `FEATURE_PICTURE_IN_PICTURE`. - Early-returned from PiP flows when unsupported. - Wrapped PiP API calls (`enterPictureInPictureMode`, `setPictureInPictureParams`) in guarded handlers that catch `IllegalStateException` and `UnsupportedOperationException`. - Logged one warning per failed PiP action to avoid log spam. ## Why On some Android devices, calling PiP APIs can throw at runtime even when the app is otherwise functioning normally. This patch prevents those calls from crashing/interrupting emulation and keeps behavior unchanged on supported devices. ## Notes - No behavior changes for fully PiP-capable devices. - Safe no-op on unsupported devices. Co-authored-by: Nikolai Trukhin Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3647 Reviewed-by: DraVee Reviewed-by: Lizzie Reviewed-by: MaranBr --- .../yuzu_emu/activities/EmulationActivity.kt | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 949edc1ee3..eab0fac705 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -15,6 +15,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.PackageManager import android.content.res.Configuration import android.graphics.Rect import android.graphics.drawable.Icon @@ -100,6 +101,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager private var romSwapGeneration = 0 private var hasEmulationSession = processHasEmulationSession private val romSwapStopTimeoutRunnable = Runnable { onRomSwapStopTimeout() } + private val pictureInPictureFailureActions: MutableSet = mutableSetOf() private fun onRomSwapStopTimeout() { if (!isWaitingForRomSwapStop) { @@ -266,12 +268,18 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager } override fun onUserLeaveHint() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) { - val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() - .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() - enterPictureInPictureMode(pictureInPictureParamsBuilder.build()) - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || + !isPictureInPictureSupported() || + !BooleanSetting.PICTURE_IN_PICTURE.getBoolean() || + isInPictureInPictureMode + ) { + return + } + + val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() + .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() + runPictureInPictureAction("enter picture-in-picture mode") { + enterPictureInPictureMode(pictureInPictureParamsBuilder.build()) } } @@ -651,7 +659,29 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager return this.apply { setActions(pictureInPictureActions) } } + private fun isPictureInPictureSupported() = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) + + private fun runPictureInPictureAction(actionName: String, action: () -> Unit) { + try { + action() + } catch (e: IllegalStateException) { + if (pictureInPictureFailureActions.add(actionName)) { + Log.warning("[PiP] Failed to $actionName: ${e.message}") + } + } catch (e: UnsupportedOperationException) { + if (pictureInPictureFailureActions.add(actionName)) { + Log.warning("[PiP] Failed to $actionName: ${e.message}") + } + } + } + fun buildPictureInPictureParams() { + if (!isPictureInPictureSupported()) { + return + } + val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -661,7 +691,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && isEmulationActive ) } - setPictureInPictureParams(pictureInPictureParamsBuilder.build()) + runPictureInPictureAction("set picture-in-picture params") { + setPictureInPictureParams(pictureInPictureParamsBuilder.build()) + } } fun displayMultiplayerDialog() {