aboutsummaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
authorcoolone <trukhin.nick@pm.me>2026-04-22 05:32:52 +0200
committercrueter <crueter@eden-emu.dev>2026-04-22 05:32:52 +0200
commit860acb4faf0b453912c451238b44f77e6a7a7fdf (patch)
tree28e18e09711df67544d72f368f74c7679106f2b2 /src/android
parenta0bb6324c0a11bf6578fe7d6951d08968fc16e12 (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.kt46
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() {