diff options
| author | coolone <trukhin.nick@pm.me> | 2026-04-22 05:32:52 +0200 |
|---|---|---|
| committer | crueter <crueter@eden-emu.dev> | 2026-04-22 05:32:52 +0200 |
| commit | 860acb4faf0b453912c451238b44f77e6a7a7fdf (patch) | |
| tree | 28e18e09711df67544d72f368f74c7679106f2b2 /src/android | |
| parent | a0bb6324c0a11bf6578fe7d6951d08968fc16e12 (diff) | |
[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 <coolone.official@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3647
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Diffstat (limited to 'src/android')
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt | 46 |
1 files 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<String> = 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() { |
