fix: overlay status bar coverage and IME input flying in portrait mode
- Restore FLAG_TRANSLUCENT_STATUS and FLAG_TRANSLUCENT_NAVIGATION on VoiceInteractionSession window to let content extend behind system bars - Move window configuration from onCreateContentView to onShow (window is guaranteed available at this point) - Replace statusBarsPadding/navigationBarsPadding with manual status bar height calculation — Compose WindowInsets may not receive proper values in VoiceInteractionSession overlay windows - Keep SOFT_INPUT_ADJUST_NOTHING + imePadding on InputArea for correct IME behavior (full-screen IME pushes input, floating IME does not) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -60,19 +60,6 @@ class CyreneVoiceInteractionSession(context: Context) :
|
||||
RuntimeLog.general("overlay", "ViewModel unavailable, overlay static")
|
||||
}
|
||||
|
||||
// Configure window: prevent IME resize, don't cover status bar
|
||||
try {
|
||||
val method = VoiceInteractionSession::class.java.getDeclaredMethod("getWindow")
|
||||
method.isAccessible = true
|
||||
val w = method.invoke(this) as? android.view.Window
|
||||
w?.apply {
|
||||
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
|
||||
clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
|
||||
lifecycleRegistry.currentState = Lifecycle.State.CREATED
|
||||
val vm = overlayViewModel
|
||||
return ComposeView(context).apply {
|
||||
@@ -102,6 +89,9 @@ class CyreneVoiceInteractionSession(context: Context) :
|
||||
RuntimeLog.general("overlay", "onShow, vm=${overlayViewModel != null}")
|
||||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
|
||||
// Configure window: extend behind status bar, don't resize for IME
|
||||
configureWindow()
|
||||
|
||||
val screenContent = CyreneAccessibilityService.getScreenContent()
|
||||
if (screenContent.isNotBlank()) {
|
||||
overlayViewModel?.sendScreenContext(screenContent)
|
||||
@@ -109,6 +99,20 @@ class CyreneVoiceInteractionSession(context: Context) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureWindow() {
|
||||
try {
|
||||
val method = VoiceInteractionSession::class.java.getDeclaredMethod("getWindow")
|
||||
method.isAccessible = true
|
||||
val w = method.invoke(this) as? android.view.Window ?: return
|
||||
w.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
w.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
||||
w.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
|
||||
Log.d(TAG, "Window configured: translucent status/nav, adjust nothing for IME")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to configure window: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHide() {
|
||||
RuntimeLog.general("overlay", "onHide")
|
||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
|
||||
@@ -18,11 +18,9 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@@ -54,6 +52,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -131,6 +131,25 @@ fun OverlayContent(
|
||||
val configuration = LocalConfiguration.current
|
||||
val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
|
||||
// Manual status bar height — Compose WindowInsets may not work in VoiceInteractionSession
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
val statusBarHeight = remember {
|
||||
val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||
if (resourceId > 0) context.resources.getDimensionPixelSize(resourceId) else 0
|
||||
}
|
||||
val statusBarPaddingDp = with(androidx.compose.ui.platform.LocalDensity.current) {
|
||||
statusBarHeight.toDp()
|
||||
}
|
||||
|
||||
// Manual nav bar height
|
||||
val navBarHeight = remember {
|
||||
val resourceId = context.resources.getIdentifier("navigation_bar_height", "dimen", "android")
|
||||
if (resourceId > 0) context.resources.getDimensionPixelSize(resourceId) else 0
|
||||
}
|
||||
val navBarPaddingDp = with(androidx.compose.ui.platform.LocalDensity.current) {
|
||||
navBarHeight.toDp()
|
||||
}
|
||||
|
||||
LaunchedEffect(messages.size) {
|
||||
if (messages.isNotEmpty()) {
|
||||
listState.animateScrollToItem(messages.size - 1)
|
||||
@@ -152,8 +171,7 @@ fun OverlayContent(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.statusBarsPadding()
|
||||
.navigationBarsPadding(),
|
||||
.padding(top = statusBarPaddingDp, bottom = navBarPaddingDp),
|
||||
) {
|
||||
if (isLandscape) {
|
||||
LandscapeContent(
|
||||
|
||||
Reference in New Issue
Block a user