Edge-to-Edge for Fullscreen PWAs on Android

โšก Chromium ๐Ÿ”ง C++ / Java / Android ๐Ÿ‘ค Helmut Januschka

Getting installed fullscreen PWAs to actually reach into the notch

Status: ๐Ÿšง In Review (almost landing)

The Problem

Install a PWA that asks for the whole screen and it still does not get the whole screen.

The canonical repro is the interop viewport demo. It is a PWA with:

Install it, launch it, and on Android it stops short of the status-bar / camera-notch region instead of drawing under it. A regular web page that calls document.documentElement.requestFullscreen() goes truly edge-to-edge, but the installed PWA does not.

This had been reported by authors repeatedly - the expectation is simple: a fullscreen PWA with viewport-fit=cover should cover the cutout area just like native fullscreen does.

Bug: 407420295

The Investigation

Android exposes LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES to let a window lay out into the display cutout on the short edges of the screen. Native fullscreen paths use it; the web app / activity flow did not opt in, so the PWA window was laid out with the default cutout mode and stayed below the notch.

Getting from "default" to "short edges" was not a one-line change. The edge-to-edge state on Android is plumbed through several layers - an edge-to-edge manager, a display cutout controller, and the token bookkeeping that decides when the decor fits system windows. The cutout mode had to be threaded through all of them, gated behind a flag, without disturbing the existing edge-to-edge behavior that other surfaces rely on.

The Fix

The change landed as a stack so each layer could be reviewed and de-risked on its own:

The controller-side CL adds support for LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES behind a delegate bit that defaults to false - a no-op on its own. The webapp/activity-flow CL is the one that actually flips fullscreen PWAs into short-edges mode. The rename CL is mechanical cleanup, moving callers off the deprecated releaseSetDecorFitsSystemWindowToken shim to the canonical releaseEdgeToEdgeToken.

Before and After

Captured on-device from the APK sampler. The notch row is the interesting one: unpatched stops below the cutout, patched draws under it.

Case 1: Fullscreen - Before
Case 1 fullscreen unpatched
Case 1: Fullscreen - After
Case 1 fullscreen patched
Case 2: Standalone - Before
Case 2 standalone unpatched
Case 2: Standalone - After
Case 2 standalone patched
Case 3: Backswipe - Before
Case 3 backswipe unpatched
Case 3: Backswipe - After
Case 3 backswipe patched

Case 4 (requestFullscreen()) already worked before the change and is unchanged - it is the control case. The sampler also has the no-notch gallery and downloadable APKs for each case.

Thanks

Big thanks to my reviewers Dan Murphy and Charles Hager for the patient back-and-forth across the stack - the layered, flag-gated approach is a direct result of their guidance on keeping a shared Android codepath safe.