The moment I dropped one of my own wallpaper apps into split-screen on a Pixel Tablet emulator, a cherry-blossom photo stretched sideways across half the display. It was an image I had picked precisely because it looked beautiful at 9:16 portrait. Something in my chest tightened a little.
Since Android 16, large screens — anything 600dp or wider at its smallest width — ignore the orientation, aspect-ratio, and resizability restrictions an app declares. Android 16 shipped with a temporary compatibility opt-out, but Android 17, expected to roll out to Pixel devices this summer, ends that grace period.
As an indie developer I run four Android wallpaper apps, all of them built on a portrait-only assumption for years. On the iOS side I had just finished replacing a decade-old SKPaymentQueue implementation with StoreKit 2, and apparently it was Android's turn next.
Tablets and foldables are not the majority of my installs. But the Play Console statistics show them growing, quietly and steadily. With Android 17 this close, looking away no longer felt like a reasonable option, so this week I started an audit together with an Antigravity agent.
What Android 17 actually stops honoring
The change applies to displays with a smallest width of 600dp or more. Phone-sized screens keep behaving the way they always have. What gets ignored is, roughly speaking, every mechanism an app uses to dictate the shape of its window:
- Orientation locks in the manifest, such as
android:screenOrientation="portrait" - Resizability refusals via
android:resizeableActivity="false" - Aspect-ratio constraints through
android:maxAspectRatio/android:minAspectRatio - Runtime calls to
setRequestedOrientation()
On Android 16 you can still keep the old behavior temporarily with a compatibility property in the manifest:
<application>
<!-- Android 16's temporary opt-out. Scheduled to lose effect on Android 17 -->
<property
android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
android:value="true" />
</application>I think of this property as one line of code that buys time, nothing more. It prevents immediate layout problems, but it comes with an expiry date. I added it to all four apps on the understanding that the real fixes would happen within the time it bought — and started the audit the same day.
Where the "wallpaper apps are portrait apps" assumption breaks
Before touching any code, I wanted to know concretely where things would break. For a wallpaper app, the damage is not limited to awkward layouts.
The first item on the list was preview trustworthiness. A wallpaper preview is a promise: this is what your home screen will look like. Render it edge-to-edge with centerCrop in a landscape window and the user sees something entirely different from what actually lands on their home screen. Nothing looks broken, yet the promise is quietly violated. To me this was the scariest failure mode of all.
Second came the crop math. The older code computed crop regions from Display.getSize(). In split-screen that call returns the window size, which no longer matches the wallpaper's target dimensions.
The rest were more straightforward: a hard-coded two-column grid that sprawls on a large screen, and activity re-creation on fold and unfold that wipes out scroll position and the current selection.
Handing the audit to an Antigravity agent
I did not have the stamina to open four repositories and eyeball them one by one, so detection went to the agent. The request had three parts:
- List every
screenOrientation/resizeableActivity/maxAspectRatiodeclaration in the manifests - Find
Display.getSize(),defaultDisplay, and hard-coded 9:16 arithmetic - Annotate each finding with a guess about what happens once the restriction is ignored
What the agent actually ran boils down to the equivalent of these two lines:
grep -rn "screenOrientation\|resizeableActivity\|maxAspectRatio" \
--include=AndroidManifest.xml .
grep -rn "defaultDisplay\|getSize(\|9f / 16f" \
--include="*.kt" --include="*.java" app/src/Across the four apps it found 14 portrait-locked activities, 6 calculations depending on getSize(), and 3 hard-coded aspect ratios. The full inventory took less than 40 minutes.
That said, the limits showed up alongside the usefulness. Detection and annotation are exactly what an agent is good at, but deciding which screens to fix and which locks to tolerate still came down to me picturing each screen one at a time. A portrait-locked settings screen, for example, causes no real harm, so I left it alone; the preview and the crop pipeline I decided to rebuild. The agent's annotations turned out to be just the right granularity as a first draft for those decisions.
Decoupling preview "correctness" from the window's shape
This was the part I thought about the longest. There were two possible directions: let the preview morph with the window, or keep rendering the wallpaper as it would actually appear, independent of the window.
I chose the latter. What a wallpaper preview owes the user is not a pleasing fit inside the current window — it is an honest picture of the home screen after the wallpaper is applied.
The implementation pivots on one idea: take the reference dimensions from WallpaperManager, not from the window.
val wm = WallpaperManager.getInstance(context)
val metrics = windowManager.currentWindowMetrics
// The wallpaper's target dimensions — independent of the window size
val targetW = wm.desiredMinimumWidth
.takeIf { it > 0 } ?: metrics.bounds.width()
val targetH = wm.desiredMinimumHeight
.takeIf { it > 0 } ?: metrics.bounds.height()
val targetAspect = targetW.toFloat() / targetHThe preview renders inside a frame with that aspect ratio and gets letterboxed into whatever window the system provides. In Compose, a centered Box plus aspectRatio is all it takes:
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
AsyncImage(
model = photo.url,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.aspectRatio(targetAspect)
)
}Because the crop region is computed from the same targetW / targetH, the preview and the applied result stay consistent even when the user works in split-screen. A landscape window does leave bars on both sides, but as the price of showing the truth about the end result, I consider it cheap.
Making verification repeatable instead of one-off
Everyone checks their app right after a fix. The real question is whether things are still intact two releases later.
My verification setup is deliberately modest: a resizable emulator device, with representative window states reproduced over adb and captured as screenshots.
adb shell wm size 1080x2424 # phone-sized window
adb exec-out screencap -p > phone.png
adb shell wm size 2208x1840 # unfolded foldable
adb exec-out screencap -p > unfolded.png
adb shell wm size resetThe agent handles this back-and-forth; I compare the images with my own eyes. I did consider automating the diffing as well, but for a wallpaper app, "correct" is ultimately a visual impression, and keeping a human at that point in the loop feels right to me.
The fold/unfold cycle surfaced one regression the static audit had not listed: the selected image vanished on re-creation. Switching that state to rememberSaveable fixed it. The fact that verification finds what scanning cannot is a useful reminder that the two are different instruments.
Next actions
If you are in a similar position, start by running those two grep lines against your own repository. Just seeing the number of portrait-locked activities turns a vague worry into a sized task, and that alone is a relief. Then buy time with the temporary opt-out, and fix the screens that carry your app's promises — like the preview — first. For me, that ordering dissolved most of the hesitation.
Portrait lock gave me the freedom not to think about window shapes for years. I read this change as that lease simply reaching its end. More window shapes ultimately mean more places for wallpapers to shine. If you maintain portrait-first apps of your own, I hope this record helps you plan the work ahead.