diff --git a/amethyst/src/main/AndroidManifest.xml b/amethyst/src/main/AndroidManifest.xml index 0a15d222cd..2435d9430b 100644 --- a/amethyst/src/main/AndroidManifest.xml +++ b/amethyst/src/main/AndroidManifest.xml @@ -559,7 +559,9 @@ + natively. The activity insets its own content for the soft keyboard (adjustResize only still + applies below Android 15, where the window is not forced edge-to-edge). Its own task/recents + entry. --> - val applied = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() - val bars = insets.getInsets(applied) - view.setPadding(bars.left, bars.top, bars.right, bars.bottom) - WindowInsetsCompat.Builder(insets).setInsets(applied, Insets.NONE).build() - } + // Pad by the system bars + cutout AND the IME: on an edge-to-edge window (enforced for targetSdk + // 35+ on Android 15+) windowSoftInputMode=adjustResize no longer shrinks the window, so without + // this the keyboard covers the bottom of the page. See applyFullScreenHostInsets. + root.applyFullScreenHostInsets() contentFrame.addView(webView, FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)) loadingView = buildLoadingView().also { contentFrame.addView(it) } diff --git a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt index 74c3c6aaac..85f0803cf2 100644 --- a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt +++ b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletHostActivity.kt @@ -56,9 +56,6 @@ import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback import androidx.core.content.ContextCompat -import androidx.core.graphics.Insets -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat import androidx.webkit.JavaScriptReplyProxy import androidx.webkit.ProxyConfig import androidx.webkit.ProxyController @@ -304,19 +301,10 @@ class NappletHostActivity : ComponentActivity() { addView(topProgressBar) } setContentView(root) - // Activities are edge-to-edge by default on recent Android; pad by the system bar and - // display-cutout insets so neither the chrome nor the applet draws under the system bars. - ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets -> - val applied = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() - val bars = insets.getInsets(applied) - view.setPadding(bars.left, bars.top, bars.right, bars.bottom) - // Zero the insets we just turned into padding before they reach the child WebView: on - // targetSdk 35+ the WebView auto-applies any insets it receives to its own web content, so - // leaving them un-consumed padded the bottom a SECOND time — a band of the page's own - // background above the navigation bar. Keep the other types (notably IME) flowing so the - // applet's keyboard-aware resize still works. - WindowInsetsCompat.Builder(insets).setInsets(applied, Insets.NONE).build() - } + // Activities are edge-to-edge by default on recent Android; pad by the system bar, display-cutout + // and IME insets so neither the chrome nor the applet draws under the system bars or the soft + // keyboard (adjustResize no longer resizes an edge-to-edge window). See applyFullScreenHostInsets. + root.applyFullScreenHostInsets() probeAndMount() } diff --git a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletWebViewInsets.kt b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletWebViewInsets.kt index f24531c91d..74374cd8be 100644 --- a/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletWebViewInsets.kt +++ b/nappletHost/src/main/kotlin/com/vitorpamplona/amethyst/napplethost/NappletWebViewInsets.kt @@ -20,11 +20,43 @@ */ package com.vitorpamplona.amethyst.napplethost +import android.view.View import android.webkit.WebView import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +/** + * Insets the root of a **full-screen** sandbox host (browser / napplet / nsite) by the system bars, + * the display cutout, **and the IME**, then hides those types from its children. + * + * The IME half is not optional on current Android. These activities used to lean on + * `windowSoftInputMode="adjustResize"` to shrink the window when the keyboard opened — the WebView + * shrank with it and the focused input stayed visible. That flag is a **no-op for apps targeting SDK + * 35+** running on Android 15+: edge-to-edge is enforced, the window keeps its full height, and the + * keyboard simply draws over the bottom of the page. Padding the root by the IME inset reproduces the + * old resize behaviour on our side of the window. + * + * The bottom is `max(bars, ime)` rather than a sum because an open IME sits *over* the navigation bar; + * adding them would leave a dead band the height of the nav bar above the keyboard. + * + * Zeroing the consumed types before they reach the children matters for the same reason + * [dropSystemBarInsets] exists: on targetSdk 35+ a WebView applies whatever insets it receives to its + * own web content, so leaving them un-consumed pads the page a second time. + */ +fun View.applyFullScreenHostInsets() { + ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets -> + val barTypes = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() + val bars = insets.getInsets(barTypes) + val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) + view.setPadding(bars.left, bars.top, bars.right, maxOf(bars.bottom, ime.bottom)) + WindowInsetsCompat + .Builder(insets) + .setInsets(barTypes or WindowInsetsCompat.Type.ime(), Insets.NONE) + .build() + } +} + /** * Stops an **embedded** sandbox WebView from inseting its own web content for the system bars. *