dvAndroidFullscreenBlocker top-level constant

String const dvAndroidFullscreenBlocker

Why display.enterFullscreen and display.exitFullscreen are unbound on Android, in one place so it is not retold three different ways.

Every route into fullscreen ends at the view hierarchy — View.setSystemUiVisibility on the decor view, or WindowInsetsController.hide from API 30 — and each of those reaches ViewRootImpl, which calls checkThread() and throws CalledFromWrongThreadException off Android's main thread. Flutter runs Dart on the UI task runner, which is a different thread, so a direct call from here would throw on every device and never here.

The fix is Activity.runOnUiThread(Runnable), and the missing piece is the Runnable: implementing a Java interface from Dart needs jnigen's generated implement, and java.lang.Runnable is a stub in the committed bindings because nothing listed it. JImplementer is public but its add is @internal and wants a native trampoline only generated code has, so there is no hand-written way round it.

So this is a one-line generation change followed by a small Dart one: add java.lang.Runnable to tool/android_bindings.jnigen.yaml, run the Android bindings workflow, then post the window work through it. It is not a platform limitation and it is not blocked on anybody — it is blocked on a generation run, which needs an Android SDK that this workspace has none of.

kiosk.enforce is unaffected and stays bound: startLockTask() is a binder call to the activity task manager and touches no views.

Implementation

const String dvAndroidFullscreenBlocker =
    'display.enterFullscreen and display.exitFullscreen need Android main '
    'thread access through Activity.runOnUiThread(Runnable), and '
    'java.lang.Runnable is a stub in the committed jnigen bindings.';