All Articles
CategoryAndroid
Reading Time
12 min read
Published
2026-06-29
Word Count
3,207words

Grab a coffee — this one is a deep dive!

Android 17 Makes Large-Screen Support Mandatory

Summary

On Android 17, screenOrientation and resizeableActivity=false are now forcibly ignored on large screens (sw>600dp); I cover what changed and how to adapt with WindowSizeClass.

  • For apps targeting API 37, on screens with a smallest width greater than 600dp, the system now ignores screenOrientation, resizeableActivity, minAspectRatio, and maxAspectRatio.
  • Games marked appCategory="game" in the manifest, and cases where the user explicitly chose the app's default behavior from device aspect-ratio settings, are exempt; screens at or below 600dp are unaffected since they're already below the threshold.
  • Before raising the target SDK, test with Android Device Streaming or Android Studio's resizable emulator, and add automated verification with the isLetterboxed() helper.
  • The durable fix for removing fixed-orientation assumptions is WindowSizeClass-based branching or moving to Material3's list-detail/supporting-pane canonical layouts.
Android 17 Makes Large-Screen Support Mandatory

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 uses resizeableActivity="false" or a fixed screenOrientation — that inventory should be the first step of your migration plan.

Table of Contents

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, including portrait, 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.

xml
1<!-- BEFORE (worked when targeting Android 16 and below) -->
2<activity
3 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<activity
9 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:

kotlin
1import android.app.Activity
2import androidx.window.layout.WindowMetricsCalculator
3 
4fun Activity.isLetterboxed(): Boolean {
5 // In multi-window mode a small window doesn't count as letterboxed
6 if (isInMultiWindowMode) return false
7 
8 val wmc = WindowMetricsCalculator.getOrCreate()
9 val currentBounds = wmc.computeCurrentWindowMetrics(this).bounds
10 val maxBounds = wmc.computeMaximumWindowMetrics(this).bounds
11 
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}
kotlin
1import androidx.test.ext.junit.rules.ActivityScenarioRule
2import androidx.test.ext.junit.runners.AndroidJUnit4
3import org.junit.Assert.assertFalse
4import org.junit.Rule
5import org.junit.Test
6import org.junit.runner.RunWith
7 
8// Test: the Activity should NOT be letterboxed on a large screen
9// JUnit4 only scans classes; a top-level @Test function silently never runs.
10@RunWith(AndroidJUnit4::class)
11class LetterboxTest {
12 
13 @get:Rule
14 val activityRule = ActivityScenarioRule(MainActivity::class.java)
15 
16 @Test
17 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:

kotlin
1import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
2import androidx.compose.runtime.Composable
3import androidx.window.core.layout.WindowSizeClass
4 
5@Composable
6fun CompactLayout() { /* single pane */ }
7 
8@Composable
9fun MediumLayout() { /* single pane, wide margins */ }
10 
11@Composable
12fun ExpandedLayout() { /* two panes side by side */ }
13 
14@Composable
15fun AdaptiveScreen() {
16 val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
17 
18 when {
19 windowSizeClass.isWidthAtLeastBreakpoint(
20 WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND
21 ) -> ExpandedLayout()
22 windowSizeClass.isWidthAtLeastBreakpoint(
23 WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND
24 ) -> MediumLayout()
25 else -> CompactLayout()
26 }
27}

If you also want to distinguish the Large and Extra-large classes, use the supportLargeAndXLargeWidth = true parameter:

kotlin
1val windowSizeClass =
2 currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClass

If 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:

kotlin
1import android.content.res.Configuration
2import android.os.Bundle
3import android.widget.FrameLayout
4import androidx.appcompat.app.AppCompatActivity
5import androidx.window.core.layout.WindowSizeClass
6import androidx.window.layout.WindowMetricsCalculator
7 
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.density
24 val container = findViewById<FrameLayout>(R.id.container)
25 if (widthDp >= WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND) {
26 // Expanded: inflate the two-pane layout
27 container.removeAllViews()
28 layoutInflater.inflate(R.layout.layout_two_pane, container, true)
29 } else {
30 // Compact/Medium: single-pane layout
31 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:

kotlin
1import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
2import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
3import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
4import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
5import androidx.compose.runtime.Composable
6import androidx.compose.runtime.rememberCoroutineScope
7import kotlinx.coroutines.launch
8 
9typealias EmailId = Long
10 
11@Composable
12fun EmailList(onEmailClick: (EmailId) -> Unit) { /* list pane */ }
13 
14@Composable
15fun EmailDetail(emailId: EmailId) { /* detail pane */ }
16 
17@OptIn(ExperimentalMaterial3AdaptiveApi::class)
18@Composable
19fun 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:

bash
1# Set the emulator's screen density and resolution to large-screen (tablet class) values
2adb shell wm size 1600x2560
3adb shell wm density 320
4 
5# Restart the app and observe the behavior
6adb shell am force-stop com.example.app
7adb shell monkey -p com.example.app -c android.intent.category.LAUNCHER 1
8 
9# Don't forget to reset back to the original size after testing
10adb shell wm size reset
11adb shell wm density reset

These 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:

  1. Build an inventory: list every Activity that uses resizeableActivity, a fixed screenOrientation, or minAspectRatio/maxAspectRatio.
  2. Test before raising the target SDK: observe current behavior with Device Streaming or the resizable emulator, and add automated testing with the isLetterboxed() helper.
  3. Move to adaptive layout: remove the fixed-orientation assumption with WindowSizeClass branching or canonical layouts (list-detail/supporting-pane).
  4. Review camera/media code: clean up any assumption of a fixed relationship between device orientation and window orientation.
  5. 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

Tags

#Android 17#API 37#large screen#WindowSizeClass#Jetpack Compose#adaptive layout#resizeableActivity
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

Lead Mobile Engineer with 12+ years of experience. Expert in iOS, Android and cross-platform architectures with Swift, SwiftUI, Kotlin and Flutter. I build performant, user-friendly mobile apps.

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.

Share