Say when Android is refusing to let a call ring

The notification was built correctly all along: CATEGORY_CALL, PRIORITY_MAX,
ongoing, autoCancel off, full-screen intent. Android was refusing it.

  USE_FULL_SCREEN_INTENT: default; rejectTime=+2m52s ago

Since Android 14 that permission is auto-granted only to apps the system
already treats as calling apps. Declared and not granted, an incoming call is
demoted to an ordinary heads-up: about two seconds at the top, then gone,
which reads as the call notification being broken.

The system refuses silently, so nothing anywhere said so. Notification
settings now checks canUseFullScreenIntent and offers the one screen that can
grant it.

The fuller fix is a self-managed ConnectionService, which makes this a calling
app in the system's eyes and needs no permission prompt at all. That is a
larger change than telling somebody the truth today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-09-10 09:06:20 -04:00
co-authored by Claude Opus 5
parent 44c7b512d6
commit df598a3961
@@ -1,5 +1,10 @@
package io.flamenet.messenger.app
import android.provider.Settings
import android.os.Build
import android.net.Uri
import android.content.Intent
import android.app.NotificationManager
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -75,6 +80,49 @@ fun ColumnScope.NotificationSection() {
Spacer(Modifier.height(Space.xs))
// A ringing call needs a permission the manifest cannot grant itself.
//
// Since Android 14, USE_FULL_SCREEN_INTENT is auto-granted only to apps the
// system already treats as calling apps. Declared and not granted, an
// incoming call is demoted to an ordinary heads-up: it shows for about two
// seconds and collapses, which reads as the call notification being broken.
// The system refuses silently, so the only way anybody learns is here.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
val manager = context.getSystemService(NotificationManager::class.java)
if (manager?.canUseFullScreenIntent() == false) {
Spacer(Modifier.height(Space.sm))
Text(
"Calls will not ring full screen",
style = MaterialTheme.typography.titleSmall,
modifier = Modifier.testTag("notify-fsi-title"),
)
Text(
"Android is holding back the permission that lets an incoming call " +
"take over the screen, so a call shows as a banner for a couple of " +
"seconds and then goes. Grant it and calls ring properly.",
fontSize = 12.sp,
color = Palette.SecondaryText,
)
Spacer(Modifier.height(Space.xs))
KitSecondaryButton(
text = "Let calls ring",
onClick = {
runCatching {
context.startActivity(
Intent(
Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT,
Uri.fromParts("package", context.packageName, null),
).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
)
}
},
tag = "notify-fsi-grant",
)
}
}
Spacer(Modifier.height(Space.xs))
// Says what turning it on costs, and does not pretend the default is merely
// cautious.
Text(