Android 17 (API 37) closes the escape hatch for apps that dodge large screens with a fixed orientation and a "non-resizable" flag. Once you raise your target SDK to 37, screenOrientation, resizeableActivity, minAspectRatio, and maxAspectRatio are ignored by the system on screens with a smallest width (sw) greater than 600dp — this isn't a suggestion, it's a runtime enforcement. In this post I walk through exactly what changed, which apps are exempt, how to test whether your app will break, and the migration path to adaptive layouts with Compose, with sources.
💡 Pro Tip: Before you raise your target SDK to 37, flag every Activity that usesresizeableActivity="false"or a fixedscreenOrientation— that inventory should be the first step of your migration plan.
Table of Contents
- What exactly changed
- Who is exempt
- Will your app break: a quick check
- Adaptive layout with WindowSizeClass
- Canonical layouts in Compose
- Orientation pitfalls for camera and media
- Test matrix for foldable and desktop mode
- Phased migration plan
- FAQ
- Why doesn't screenOrientation=portrait work on Android 17?
- Is resizeableActivity=false still valid?
- How do I adapt my app to large screens?
- Which apps are exempt from the large-screen requirement?
- Does this change affect all Android 17 devices?
- Do I need a real device to test?
- Conclusion
- Sources
What exactly changed
For apps targeting Android 17 (API level 37+), the system now ignores the following manifest attributes and runtime calls on screens with a smallest width greater than 600dp — in both fullscreen and multi-window mode:
android:screenOrientation: all fixed values are ineffective, includingportrait,reversePortrait,sensorPortrait,userPortrait,landscape,reverseLandscape,sensorLandscape,userLandscape.android:resizeableActivity="false": no longer has any effect; the system treats the app as resizable regardless.android:minAspectRatio/android:maxAspectRatio: aspect-ratio constraints are not applied.setRequestedOrientation()/getRequestedOrientation(): even called at runtime, they have no effect on a large screen.
The two official pages word the threshold differently: the primary behavior-change page says "greater than 600dp" (only above 600dp) — that is the wording I follow throughout this article; the compatibility-mode guide describes the same API 37 behavior as "at least sw600dp" (600dp included). Above 600dp the system certainly ignores these attributes; at the exact 600dp boundary, verify the behavior on your own reference device. This isn't a new restriction; it closes a temporary opt-out Android 16 granted developers. So "why now" is simple: the transition period is over, and the permanent rule is in effect.
In practice: an Activity locked to portrait that works perfectly on your phone can now be forcibly resized by the system on a tablet or unfolded foldable, with your orientation constraint ignored. If your app isn't ready, its UI may appear stretched, cropped, or misplaced.
1<!-- BEFORE (worked when targeting Android 16 and below) -->2<activity3 android:name=".MainActivity"4 android:screenOrientation="portrait"5 android:resizeableActivity="false" />6 7<!-- AFTER (targetSdk=37, these attributes are IGNORED on screens with sw>600dp) -->8<activity9 android:name=".MainActivity" />10<!-- Adaptive layout code instead of orientation constraints (see WindowSizeClass section) -->Who is exempt
There are three exceptions to the enforcement:
- Games: apps marked with
android:appCategory="game"in the manifest are exempt from these restrictions. - User preference: if the user explicitly chooses the app's default behavior from the device's aspect-ratio settings, the restrictions don't apply.
- Small screens: on devices with a smallest width of 600dp or below (the vast majority of standard phones), these attributes keep working, so those devices never see the behavior change at all.
Tablets, foldables (unfolded), desktop windowing mode, and externally connected displays — all of these easily exceed the 600dp threshold, so the system ignores these attributes on all of them.
Will your app break: a quick check
Before raising your target SDK, you need to verify your current behavior on real reference devices. The official guidance recommends two testing paths: access to real large-screen devices through Firebase-backed Android Device Streaming, and local testing with Android Studio's resizable emulator.
You can also verify letterbox (black-bar, centered) behavior programmatically:
1import android.app.Activity2import androidx.window.layout.WindowMetricsCalculator3 4fun Activity.isLetterboxed(): Boolean {5 // In multi-window mode a small window doesn't count as letterboxed6 if (isInMultiWindowMode) return false7 8 val wmc = WindowMetricsCalculator.getOrCreate()9 val currentBounds = wmc.computeCurrentWindowMetrics(this).bounds10 val maxBounds = wmc.computeMaximumWindowMetrics(this).bounds11 12 val isScreenPortrait = maxBounds.height() > maxBounds.width()13 14 return if (isScreenPortrait) {15 currentBounds.height() < maxBounds.height()16 } else {17 currentBounds.width() < maxBounds.width()18 }19}1import androidx.test.ext.junit.rules.ActivityScenarioRule2import androidx.test.ext.junit.runners.AndroidJUnit43import org.junit.Assert.assertFalse4import org.junit.Rule5import org.junit.Test6import org.junit.runner.RunWith7 8// Test: the Activity should NOT be letterboxed on a large screen9// JUnit4 only scans classes; a top-level @Test function silently never runs.10@RunWith(AndroidJUnit4::class)11class LetterboxTest {12 13 @get:Rule14 val activityRule = ActivityScenarioRule(MainActivity::class.java)15 16 @Test17 fun activity_launched_notLetterBoxed() {18 activityRule.scenario.onActivity { activity ->19 assertFalse(activity.isLetterboxed())20 }21 }22}Recommended testing order: first, without changing your target SDK, run your current APK on a large-screen reference device and observe the orientation constraint the system currently applies in your favor. Then add the isLetterboxed() helper to your app's main screens and run it automatically in CI, so you catch regressions early on every build. Finally, create a separate debug flavor that temporarily sets the target SDK to 37, so you can observe the behavior without breaking your release configuration.
Make sure your test matrix covers at least these device categories:
Device category | Natural orientation / trait | Watch out for |
|---|---|---|
Tablets | Some are landscape by nature | Portrait-locked layouts break |
Landscape foldables (e.g. Pixel Fold) | Portrait when folded, landscape when unfolded | Orientation change at fold/unfold must be tested |
Foldable flip phones | Small landscape screen when folded, portrait when unfolded | Transition between two different sw values |
External displays | Can start a desktop windowing session | Display connect/disconnect scenario |
In-car displays | Usually landscape | Fixed-orientation assumptions are riskiest here |
Each category can surface a need for per-app overrides — so a single tablet test isn't enough; include fold/unfold and display-connect moments in your test matrix too.
Adaptive layout with WindowSizeClass
Instead of a fixed orientation, you need to move to an adaptive approach that picks a layout based on window size. The Compose Material3 adaptive library divides width into classes:
Width class | Range |
|---|---|
Compact | width < 600dp |
Medium | 600dp ≤ width < 840dp |
Expanded | 840dp ≤ width < 1200dp |
Large | 1200dp ≤ width < 1600dp |
Extra-large | width ≥ 1600dp |
The critical point: these classes are window-based, not device-based, and dynamic — they can change on orientation change, multi-window, and fold/unfold. So computing the class once and caching it is wrong; you need to read the current value on every recomposition.
On the Compose side, the branching looks like this:
1import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo2import androidx.compose.runtime.Composable3import androidx.window.core.layout.WindowSizeClass4 5@Composable6fun CompactLayout() { /* single pane */ }7 8@Composable9fun MediumLayout() { /* single pane, wide margins */ }10 11@Composable12fun ExpandedLayout() { /* two panes side by side */ }13 14@Composable15fun AdaptiveScreen() {16 val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass17 18 when {19 windowSizeClass.isWidthAtLeastBreakpoint(20 WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND21 ) -> ExpandedLayout()22 windowSizeClass.isWidthAtLeastBreakpoint(23 WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND24 ) -> MediumLayout()25 else -> CompactLayout()26 }27}If you also want to distinguish the Large and Extra-large classes, use the supportLargeAndXLargeWidth = true parameter:
1val windowSizeClass =2 currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClassIf you haven't fully migrated to Compose yet and have a View-based (XML layout) app, you can use the same androidx.window infrastructure — WindowSizeClass isn't Compose-specific; it's also accessible in the View world via WindowMetricsCalculator:
1import android.content.res.Configuration2import android.os.Bundle3import android.widget.FrameLayout4import androidx.appcompat.app.AppCompatActivity5import androidx.window.core.layout.WindowSizeClass6import androidx.window.layout.WindowMetricsCalculator7 8class MainActivity : AppCompatActivity() {9 override fun onCreate(savedInstanceState: Bundle?) {10 super.onCreate(savedInstanceState)11 setContentView(R.layout.activity_main)12 updateLayoutForWindowSize()13 }14 15 override fun onConfigurationChanged(newConfig: Configuration) {16 super.onConfigurationChanged(newConfig)17 updateLayoutForWindowSize()18 }19 20 private fun updateLayoutForWindowSize() {21 val metrics = WindowMetricsCalculator.getOrCreate()22 .computeCurrentWindowMetrics(this)23 val widthDp = metrics.bounds.width() / resources.displayMetrics.density24 val container = findViewById<FrameLayout>(R.id.container)25 if (widthDp >= WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND) {26 // Expanded: inflate the two-pane layout27 container.removeAllViews()28 layoutInflater.inflate(R.layout.layout_two_pane, container, true)29 } else {30 // Compact/Medium: single-pane layout31 container.removeAllViews()32 layoutInflater.inflate(R.layout.layout_single_pane, container, true)33 }34 }35}onConfigurationChanged is only called when the relevant configChanges values are declared in the manifest; if not declared, the Activity is recreated and the size calculation goes through the onCreate path. This approach isn't as elegant as Compose (you have to inflate the layout manually), but it's a practical way for teams migrating incrementally to remove the fixed-orientation assumption before fully moving to Compose.
Canonical layouts in Compose
Instead of branching on WindowSizeClass by hand, using the Material3 adaptive library's ready-made canonical layout components is less error-prone. Two patterns stand out:
- List-detail: the pattern where the user browses a list of items and sees descriptive extra information for each item. It's implemented with
ListDetailPaneScaffold+rememberListDetailPaneScaffoldNavigator(); at expanded width the list and detail show side by side, and at compact/medium width a single pane is shown. - Supporting pane: splits content into primary and secondary areas; the official guidance recommends 70% of the area to main content and 30% to supporting content at expanded width, split evenly at medium width. The difference: list-detail's detail pane is meaningful without the main content, while supporting pane's secondary content only makes sense alongside the primary content. Implemented with
SupportingPaneScaffold+rememberSupportingPaneScaffoldNavigator().
Since both components carry their own navigator, transitions between panes and state restoration aren't something you write by hand: the navigator holds the back stack, and you can configure back behavior with BackNavigationBehavior. This is a real maintenance advantage over hand-written if (windowSizeClass == Compact) { ... } else { ... } branching: no manual navigation state sync every time the window size changes.
The simplest form of the list-detail pattern looks like this:
1import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi2import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold3import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole4import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator5import androidx.compose.runtime.Composable6import androidx.compose.runtime.rememberCoroutineScope7import kotlinx.coroutines.launch8 9typealias EmailId = Long10 11@Composable12fun EmailList(onEmailClick: (EmailId) -> Unit) { /* list pane */ }13 14@Composable15fun EmailDetail(emailId: EmailId) { /* detail pane */ }16 17@OptIn(ExperimentalMaterial3AdaptiveApi::class)18@Composable19fun EmailApp() {20 val scope = rememberCoroutineScope()21 val navigator = rememberListDetailPaneScaffoldNavigator<EmailId>()22 23 ListDetailPaneScaffold(24 directive = navigator.scaffoldDirective,25 value = navigator.scaffoldValue,26 listPane = {27 EmailList(onEmailClick = { id ->28 scope.launch {29 navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, id)30 }31 })32 },33 detailPane = {34 navigator.currentDestination?.contentKey?.let { id ->35 EmailDetail(emailId = id)36 }37 }38 )39}When deciding which pattern to pick, ask this question: is the secondary panel's content meaningful on its own, without the primary content? If the answer is "yes," go with list-detail; if "no, it only makes sense in context," supporting pane is the right choice.
Orientation pitfalls for camera and media
The official documentation warns: camera app previews (viewfinders) can appear misaligned or distorted on tablets, laptops, and foldable screens. The root cause is that apps assume a fixed relationship between camera characteristics (aspect ratio, sensor orientation) and device characteristics (device orientation, natural orientation) — in Android 17 this assumption no longer holds, because device orientation and window orientation can now diverge.
The typical break point is this: if you read the sensor orientation from the device's physical orientation and apply a fixed transformation matrix when building the camera preview's SurfaceView, that matrix no longer matches the actual window orientation once the user opens your app in a rotated window under desktop windowing mode. The safe approach is to recompute the transformation matrix every time the window configuration changes (via onConfigurationChanged, or by reading LocalConfiguration in Compose), rather than assuming the device's physical orientation is the single source of truth.
Test matrix for foldable and desktop mode
When turning the device category list above into a test flow, focus especially on three transition moments: fold/unfold, entering/exiting multi-window, and connecting/disconnecting an external display. These three transitions cause the same Activity instance's sw value to change at runtime — this is exactly where code that assumes a fixed orientation blows up.
- Foldable flip phone: small landscape screen when folded, portrait when unfolded — test the transition between the two different windowSizeClass values without losing state.
- Landscape foldable (Pixel Fold type): portrait when folded, landscape when unfolded — the orientation assumption is reversed.
- Desktop windowing: some devices can start a desktop windowing session when connected to an external display; your app should be ready to run in a freely resizable window.
For a quick manual check on the emulator side, you can change a running emulator's window size directly from the command line and observe your app's behavior:
1# Set the emulator's screen density and resolution to large-screen (tablet class) values2adb shell wm size 1600x25603adb shell wm density 3204 5# Restart the app and observe the behavior6adb shell am force-stop com.example.app7adb shell monkey -p com.example.app -c android.intent.category.LAUNCHER 18 9# Don't forget to reset back to the original size after testing10adb shell wm size reset11adb shell wm density resetThese commands aren't a substitute for a real tablet or foldable device, but they're a useful quick health check, especially early in migration, to confirm your layout opens at a large width without crashing. For real regression hunting, you still need Device Streaming or the resizable emulator, manually walking through the full set of user flows; a command-line size change only speeds up initial triage.
Phased migration plan
Android 17 stable rolled out to Pixel devices on June 16, 2026. So by this article's original publish date of June 29, 2026, the restriction was already in effect. The recommended migration order:
- Build an inventory: list every Activity that uses
resizeableActivity, a fixedscreenOrientation, orminAspectRatio/maxAspectRatio. - Test before raising the target SDK: observe current behavior with Device Streaming or the resizable emulator, and add automated testing with the
isLetterboxed()helper. - Move to adaptive layout: remove the fixed-orientation assumption with WindowSizeClass branching or canonical layouts (list-detail/supporting-pane).
- Review camera/media code: clean up any assumption of a fixed relationship between device orientation and window orientation.
- Raise the target SDK to 37 and re-run the full test matrix (tablet, foldable, desktop, external display).
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
Reader Reward
I've put together a short checklist you can use before raising your target SDK to 37 — each item corresponds to a step covered in this post, and if you work through it in order, you'll catch most large-screen regressions before they reach production.
FAQ
Why doesn't screenOrientation=portrait work on Android 17?
For apps targeting API 37 on a screen with a smallest width greater than 600dp, android:screenOrientation (all fixed values, portrait included) is ignored by the system. This is the direct result of Android 17 fully removing the temporary developer opt-out Android 16 granted.
Is resizeableActivity=false still valid?
No. For apps targeting API 37, android:resizeableActivity="false" produces no effect on large screens; the system still treats the app as resizable.
How do I adapt my app to large screens?
Remove the fixed-orientation and aspect-ratio assumptions and build a window-size-aware interface with WindowSizeClass-based branching or Material3's canonical layouts (list-detail, supporting-pane). Also review your camera/media code for a fixed relationship between device orientation and window orientation.
Which apps are exempt from the large-screen requirement?
Games marked with android:appCategory="game" in the manifest are exempt. The restrictions also don't apply if the user explicitly chooses the app's default behavior from the device's aspect-ratio settings. Devices with a smallest width of 600dp or below fall outside the threshold, so they never see the restriction at all.
Does this change affect all Android 17 devices?
No, it only affects apps whose target SDK is 37 or higher. On a device running Android 17, an app with a lower target SDK keeps its old behavior, including fixed orientation.
Do I need a real device to test?
Not necessarily — you can access a real device remotely with Firebase-backed Android Device Streaming, or test locally with Android Studio's resizable emulator; both are officially recommended paths.
Conclusion
Android 17's large-screen enforcement isn't really a new restriction — it's the closing of a temporary escape hatch that's existed since Android 16. Before raising your target SDK to 37, inventory your usage of fixed orientation and resizeableActivity, test on a real device or emulator, then move to an adaptive interface with WindowSizeClass or canonical layouts. For broader context, see Android 17 (API 37): 6 Changes That Will Break Your App, covering the other behavior changes in the same release. On the Compose side, Jetpack Compose 1.7 Performance: Strong Skipping + Stability pairs performance with adaptive layout. On design language, Material 3 Expressive: Android 16 Design System clarifies which visual language to anchor your adaptive layout to. Android 15 Developer Guide: Privacy Sandbox, Edge-to-Edge, Foreground Services summarizes the other requirements you'll hit during a target SDK upgrade. If you missed the Google Play target SDK deadline, Google Play API 36 Deadline Passed: What to Do Now? offers a roadmap.
Sources
- Android 17 changes: FF restrictions ignored — the primary source for the sw>600dp threshold, the ignored manifest attributes, and the three exemptions.
- Android Developers Blog: Android 17 announcement — the June 16, 2026 stable release announcement and the call for adaptive-first development.
- Android 17 release notes — the official list of the release's behavior changes.
- Large screen compatibility mode guide — test device categories, and the Device Streaming and resizable emulator recommendations.
- Canonical layouts (Compose adaptive) — the official definition of the list-detail and supporting-pane patterns.
- WindowSizeClass usage guide — width/height classes and the currentWindowAdaptiveInfo() API.
- WindowSizeClass API reference — the WIDTH_DP_* constants (600/840/1200/1600) and the isWidthAtLeastBreakpoint signature.
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
We respect your privacy. You can unsubscribe at any time.

