dvAndroidFullscreenBlocker top-level constant
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.';