I bumped targetSdk to 36, rebuilt, opened the usual wallpaper preview screen, and the top toolbar was sitting right on top of the status bar clock. The trick I had been leaning on — setting windowOptOutEdgeToEdgeEnforcement to true — no longer worked on Android 16.
I have been shipping iOS and Android apps on my own since 2014, mostly wallpaper and calming-themed apps. "50 million cumulative downloads" sounds impressive as a number, but in practice it means I run a fleet of structurally similar apps in parallel, and every OS-level behavior change forces me to fix all of them at once. The edge-to-edge enforcement was a textbook example. This article is less a step-by-step tutorial and more a record of where I let Antigravity's agent do the work and where I insisted on checking things with my own eyes.
Why "later" was not an option
On Android 15 (API 35), apps targeting SDK 35 already went edge-to-edge by default. But back then you could revert to the old behavior by setting windowOptOutEdgeToEdgeEnforcement to true, and honestly, I leaned on that opt-out to keep putting the work off.
Android 16 (API 36) changes the rules. For apps targeting SDK 36, that attribute is deprecated and disabled — you can no longer refuse edge-to-edge. To be precise about the behavior: an app targeting SDK 36 still honors the opt-out while running on an Android 15 device, but the opt-out is disabled on an Android 16 device. In other words, as user devices update, full-screen layout arrives whether I act or not.
There was a second reason I stopped postponing. A wallpaper preview screen is actually a natural fit for edge-to-edge. I want the image to reach every corner of the display, so the margin I had been reserving under the system bars was a loss, not a feature. Reframing this from "a chore I'll get to someday" to "a change that improves the experience today" was what finally got me moving.
First, remove the opt-out everywhere
The first task was deleting every windowOptOutEdgeToEdgeEnforcement that had been my escape hatch. I asked Antigravity's Agent Mode to enumerate every place the attribute was used across the repository. It lived not only in themes.xml but, in a couple of apps, in per-Activity theme overrides — spots I would have missed working from memory alone.
<!-- Before: I relied on this -->
<style name="Theme.WallpaperApp" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<!-- After: drop the attribute entirely -->
<style name="Theme.WallpaperApp" parent="Theme.Material3.DayNight.NoActionBar">
<!-- switch to accepting edge-to-edge as the baseline -->
</style>While I was at it, android:statusBarColor and android:navigationBarColor are also deprecated under targetSdk 36. I abandoned the whole idea of hiding the bars by painting them a color, and switched to a policy of letting the bars stay transparent with content drawn underneath.
Rewrite each screen to be the one that receives insets
Once the opt-out is gone, content predictably slips under the system bars. This is the real work: each screen needs to receive the insets (the region the system bars occupy) and apply its own padding.
For older View-based Activities, the reliable recipe was to call enableEdgeToEdge() and then attach an inset listener to the root view.
import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
class PreviewActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_preview)
// Keep the wallpaper full-bleed; only the control UI dodges the bars.
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.toolbar)) { v, insets ->
val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(v.paddingLeft, bars.top, v.paddingRight, v.paddingBottom)
insets
}
}
}Where I tripped: on one screen I had left fitsSystemWindows="true" in the XML and written an inset listener, so padding was applied twice and the margin actually grew. On Android 16 I found it cleanest to remove fitsSystemWindows and funnel all inset handling into a single place.
For the newer apps already on Jetpack Compose, it was more straightforward. Either pass Scaffold's innerPadding inward, or — better for a wallpaper — leave the full-screen image layer untouched and apply safeDrawingPadding() only to the control layer.
Box(Modifier.fillMaxSize()) {
// Draw the wallpaper to every corner (no insets here)
WallpaperImage(modifier = Modifier.fillMaxSize())
// Controls dodge the system bars
PreviewControls(
modifier = Modifier
.align(Alignment.TopCenter)
.safeDrawingPadding(),
)
}That distinction — "don't pad the image, pad only the UI" — is, I think, the crux of edge-to-edge for a wallpaper app. Slap safeDrawingPadding() on everything and you throw away the very full-screen preview you just earned.
Where I used Antigravity, and where I didn't
Across this multi-app effort, the agent earned its keep on mechanically enumerable work: listing the opt-out attributes, locating every statusBarColor usage, and proposing candidate Activities with no inset listener. Handing that to the agent was both faster and more reliable than my own scan.
The final fit of the padding, though, I always verified on both a physical device and an emulator. Inset values differ by device — gesture nav versus button nav especially — so code that looks correct can still render misaligned. My grandfathers were temple carpenters, and I am told that even when the drawings matched, they always confirmed the final fit by hand. Going through the screens one by one, I felt this kind of finishing check is the part a human should keep. Drawing a clear line between what to delegate and what to verify yourself is, in fact, the biggest time-saver on cross-app work.
What comes next
I have finished the first pass across the wallpaper apps and am now nailing down the display on three-button and gesture-nav devices on real hardware. Next, I want to extract the inset handling — currently copy-pasted across every app — into a shared module. My near-term goal is to gradually lighten the very structure that forces me to fix every app each time the OS shifts.
If you are facing a targetSdk 36 migration with several apps of your own, I would start by enumerating every opt-out usage across the codebase; it makes the scope of the work far easier to estimate. Thank you for reading.