diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bfd49353..dcf17a0af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
-
- Compatibility updates for Android 15 & 16
+### Fixed
+- Fixed incoming call screen hidden by lock screen ([#165])
+
## [1.7.3] - 2025-10-16
### Changed
- Updated translations
@@ -203,6 +205,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#139]: https://github.com/FossifyOrg/Phone/issues/139
[#146]: https://github.com/FossifyOrg/Phone/issues/146
[#147]: https://github.com/FossifyOrg/Phone/issues/147
+[#165]: https://github.com/FossifyOrg/Phone/issues/165
[#181]: https://github.com/FossifyOrg/Phone/issues/181
[#183]: https://github.com/FossifyOrg/Phone/issues/183
[#186]: https://github.com/FossifyOrg/Phone/issues/186
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3bb236867..5bb585835 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,6 +21,7 @@
+
+ handleFullScreenNotificationsPermission { granted ->
if (!granted) {
- PermissionRequiredDialog(this, R.string.allow_notifications_incoming_calls, { openNotificationSettings() })
+ toast(org.fossify.commons.R.string.notifications_disabled)
}
}
} else {
diff --git a/app/src/main/kotlin/org/fossify/phone/extensions/Activity.kt b/app/src/main/kotlin/org/fossify/phone/extensions/Activity.kt
index 5345f1a2a..0454ebc4b 100644
--- a/app/src/main/kotlin/org/fossify/phone/extensions/Activity.kt
+++ b/app/src/main/kotlin/org/fossify/phone/extensions/Activity.kt
@@ -8,14 +8,19 @@ import android.provider.ContactsContract
import android.telecom.PhoneAccount
import android.telecom.PhoneAccountHandle
import android.telecom.TelecomManager
+import org.fossify.commons.R
import org.fossify.commons.activities.BaseSimpleActivity
import org.fossify.commons.dialogs.CallConfirmationDialog
+import org.fossify.commons.dialogs.PermissionRequiredDialog
+import org.fossify.commons.extensions.canUseFullScreenIntent
import org.fossify.commons.extensions.initiateCall
import org.fossify.commons.extensions.isDefaultDialer
import org.fossify.commons.extensions.isPackageInstalled
import org.fossify.commons.extensions.launchActivityIntent
import org.fossify.commons.extensions.launchCallIntent
import org.fossify.commons.extensions.launchViewContactIntent
+import org.fossify.commons.extensions.openFullScreenIntentSettings
+import org.fossify.commons.extensions.openNotificationSettings
import org.fossify.commons.extensions.telecomManager
import org.fossify.commons.helpers.CONTACT_ID
import org.fossify.commons.helpers.IS_PRIVATE
@@ -23,6 +28,7 @@ import org.fossify.commons.helpers.PERMISSION_READ_PHONE_STATE
import org.fossify.commons.helpers.SimpleContactsHelper
import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.models.contacts.Contact
+import org.fossify.phone.BuildConfig
import org.fossify.phone.activities.DialerActivity
import org.fossify.phone.activities.SimpleActivity
import org.fossify.phone.dialogs.SelectSIMDialog
@@ -181,3 +187,36 @@ fun SimpleActivity.showSelectSimDialog(
) { handle ->
callback(handle)
}
+
+fun SimpleActivity.handleFullScreenNotificationsPermission(callback: (granted: Boolean) -> Unit) {
+ handleNotificationPermission { granted ->
+ if (granted) {
+ if (canUseFullScreenIntent()) {
+ callback(true)
+ } else {
+ PermissionRequiredDialog(
+ activity = this,
+ textId = R.string.allow_full_screen_notifications_incoming_calls,
+ positiveActionCallback = {
+ @SuppressLint("NewApi")
+ openFullScreenIntentSettings(BuildConfig.APPLICATION_ID)
+ },
+ negativeActionCallback = {
+ callback(false)
+ }
+ )
+ }
+ } else {
+ PermissionRequiredDialog(
+ activity = this,
+ textId = R.string.allow_notifications_incoming_calls,
+ positiveActionCallback = {
+ openNotificationSettings()
+ },
+ negativeActionCallback = {
+ callback(false)
+ }
+ )
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/fossify/phone/extensions/Context.kt b/app/src/main/kotlin/org/fossify/phone/extensions/Context.kt
index d6b14ca8e..4fe8ecf32 100644
--- a/app/src/main/kotlin/org/fossify/phone/extensions/Context.kt
+++ b/app/src/main/kotlin/org/fossify/phone/extensions/Context.kt
@@ -1,7 +1,9 @@
package org.fossify.phone.extensions
import android.annotation.SuppressLint
+import android.app.KeyguardManager
import android.content.Context
+import android.content.Context.KEYGUARD_SERVICE
import android.content.Intent
import android.media.AudioManager
import android.net.Uri
@@ -14,9 +16,14 @@ import org.fossify.phone.models.SIMAccount
val Context.config: Config get() = Config.newInstance(applicationContext)
-val Context.audioManager: AudioManager get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager
+val Context.audioManager: AudioManager
+ get() = getSystemService(Context.AUDIO_SERVICE) as AudioManager
-val Context.powerManager: PowerManager get() = getSystemService(Context.POWER_SERVICE) as PowerManager
+val Context.powerManager: PowerManager
+ get() = getSystemService(Context.POWER_SERVICE) as PowerManager
+
+val Context.keyguardManager: KeyguardManager
+ get() = getSystemService(KEYGUARD_SERVICE) as KeyguardManager
@SuppressLint("MissingPermission")
fun Context.getAvailableSIMCardLabels(): List {
diff --git a/app/src/main/kotlin/org/fossify/phone/helpers/CallNotificationManager.kt b/app/src/main/kotlin/org/fossify/phone/helpers/CallNotificationManager.kt
index a5fc13a64..90b527e9e 100644
--- a/app/src/main/kotlin/org/fossify/phone/helpers/CallNotificationManager.kt
+++ b/app/src/main/kotlin/org/fossify/phone/helpers/CallNotificationManager.kt
@@ -3,64 +3,65 @@ package org.fossify.phone.helpers
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
-import android.app.NotificationManager
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.telecom.Call
import android.widget.RemoteViews
-import androidx.core.app.NotificationCompat
import org.fossify.commons.extensions.notificationManager
import org.fossify.commons.extensions.setText
import org.fossify.commons.extensions.setVisibleIf
-import org.fossify.commons.helpers.isOreoPlus
import org.fossify.phone.R
import org.fossify.phone.activities.CallActivity
-import org.fossify.phone.extensions.powerManager
import org.fossify.phone.receivers.CallActionReceiver
class CallNotificationManager(private val context: Context) {
- private val CALL_NOTIFICATION_ID = 42
- private val ACCEPT_CALL_CODE = 0
- private val DECLINE_CALL_CODE = 1
+ companion object {
+ private const val CALL_NOTIFICATION_ID = 42
+ private const val ACCEPT_CALL_CODE = 0
+ private const val DECLINE_CALL_CODE = 1
+ }
+
private val notificationManager = context.notificationManager
private val callContactAvatarHelper = CallContactAvatarHelper(context)
@SuppressLint("NewApi")
- fun setupNotification(forceLowPriority: Boolean = false) {
+ fun setupNotification(lowPriority: Boolean = false) {
getCallContact(context.applicationContext, CallManager.getPrimaryCall()) { callContact ->
val callContactAvatar = callContactAvatarHelper.getCallContactAvatar(callContact)
val callState = CallManager.getState()
- val isHighPriority = context.powerManager.isInteractive && callState == Call.STATE_RINGING && !forceLowPriority
- val channelId = if (isHighPriority) "simple_dialer_call_high_priority" else "simple_dialer_call"
- if (isOreoPlus()) {
- val importance = if (isHighPriority) NotificationManager.IMPORTANCE_HIGH else NotificationManager.IMPORTANCE_DEFAULT
- val name = if (isHighPriority) {
- context.getString(R.string.call_notification_channel_high_priority)
- } else {
- context.getString(R.string.call_notification_channel)
- }
-
- NotificationChannel(channelId, name, importance).apply {
- setSound(null, null)
- notificationManager.createNotificationChannel(this)
- }
- }
+ val isHighPriority = callState == Call.STATE_RINGING && !lowPriority
+ val channelId =
+ if (isHighPriority) "simple_dialer_call_high_priority" else "simple_dialer_call"
+ createNotificationChannel(isHighPriority, channelId)
val openAppIntent = CallActivity.getStartIntent(context)
- val openAppPendingIntent = PendingIntent.getActivity(context, 0, openAppIntent, PendingIntent.FLAG_MUTABLE)
+ val openAppPendingIntent =
+ PendingIntent.getActivity(context, 0, openAppIntent, PendingIntent.FLAG_MUTABLE)
val acceptCallIntent = Intent(context, CallActionReceiver::class.java)
acceptCallIntent.action = ACCEPT_CALL
val acceptPendingIntent =
- PendingIntent.getBroadcast(context, ACCEPT_CALL_CODE, acceptCallIntent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE)
+ PendingIntent.getBroadcast(
+ context,
+ ACCEPT_CALL_CODE,
+ acceptCallIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
val declineCallIntent = Intent(context, CallActionReceiver::class.java)
declineCallIntent.action = DECLINE_CALL
val declinePendingIntent =
- PendingIntent.getBroadcast(context, DECLINE_CALL_CODE, declineCallIntent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE)
-
- var callerName = if (callContact.name.isNotEmpty()) callContact.name else context.getString(R.string.unknown_caller)
+ PendingIntent.getBroadcast(
+ context,
+ DECLINE_CALL_CODE,
+ declineCallIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+
+ var callerName = callContact.name.ifEmpty { context.getString(R.string.unknown_caller) }
if (callContact.numberLabel.isNotEmpty()) {
callerName += " - ${callContact.numberLabel}"
}
@@ -82,21 +83,22 @@ class CallNotificationManager(private val context: Context) {
setOnClickPendingIntent(R.id.notification_accept_call, acceptPendingIntent)
if (callContactAvatar != null) {
- setImageViewBitmap(R.id.notification_thumbnail, callContactAvatarHelper.getCircularBitmap(callContactAvatar))
+ setImageViewBitmap(
+ R.id.notification_thumbnail,
+ callContactAvatarHelper.getCircularBitmap(callContactAvatar)
+ )
}
}
- val builder = NotificationCompat.Builder(context, channelId)
+ val builder = Notification.Builder(context, channelId)
.setSmallIcon(R.drawable.ic_phone_vector)
.setContentIntent(openAppPendingIntent)
- .setPriority(if (isHighPriority) NotificationManager.IMPORTANCE_HIGH else NotificationCompat.PRIORITY_DEFAULT)
.setCategory(Notification.CATEGORY_CALL)
.setCustomContentView(collapsedView)
.setOngoing(true)
- .setSound(null)
.setUsesChronometer(callState == Call.STATE_ACTIVE)
.setChannelId(channelId)
- .setStyle(NotificationCompat.DecoratedCustomViewStyle())
+ .setStyle(Notification.DecoratedCustomViewStyle())
if (isHighPriority) {
builder.setFullScreenIntent(openAppPendingIntent, true)
@@ -110,6 +112,20 @@ class CallNotificationManager(private val context: Context) {
}
}
+ fun createNotificationChannel(isHighPriority: Boolean, channelId: String) {
+ val name = if (isHighPriority) {
+ context.getString(R.string.call_notification_channel_high_priority)
+ } else {
+ context.getString(R.string.call_notification_channel)
+ }
+
+ val importance = if (isHighPriority) IMPORTANCE_HIGH else IMPORTANCE_DEFAULT
+ NotificationChannel(channelId, name, importance).apply {
+ setSound(null, null)
+ notificationManager.createNotificationChannel(this)
+ }
+ }
+
fun cancelNotification() {
notificationManager.cancel(CALL_NOTIFICATION_ID)
}
diff --git a/app/src/main/kotlin/org/fossify/phone/services/CallService.kt b/app/src/main/kotlin/org/fossify/phone/services/CallService.kt
index daa669378..de161d030 100644
--- a/app/src/main/kotlin/org/fossify/phone/services/CallService.kt
+++ b/app/src/main/kotlin/org/fossify/phone/services/CallService.kt
@@ -1,13 +1,15 @@
package org.fossify.phone.services
-import android.app.KeyguardManager
-import android.content.Context
import android.telecom.Call
import android.telecom.CallAudioState
import android.telecom.InCallService
+import org.fossify.commons.extensions.canUseFullScreenIntent
+import org.fossify.commons.extensions.hasPermission
+import org.fossify.commons.helpers.PERMISSION_POST_NOTIFICATIONS
import org.fossify.phone.activities.CallActivity
import org.fossify.phone.extensions.config
import org.fossify.phone.extensions.isOutgoing
+import org.fossify.phone.extensions.keyguardManager
import org.fossify.phone.extensions.powerManager
import org.fossify.phone.helpers.CallManager
import org.fossify.phone.helpers.CallNotificationManager
@@ -35,17 +37,31 @@ class CallService : InCallService() {
CallManager.inCallService = this
call.registerCallback(callListener)
- val isScreenLocked = (getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager).isDeviceLocked
- if (!powerManager.isInteractive || call.isOutgoing() || isScreenLocked || config.alwaysShowFullscreen) {
+ // Incoming/Outgoing (locked): high priority (FSI)
+ // Incoming (unlocked): if user opted in, low priority ➜ manual activity start, otherwise high priority (FSI)
+ // Outgoing (unlocked): low priority ➜ manual activity start
+ val isIncoming = !call.isOutgoing()
+ val isDeviceLocked = !powerManager.isInteractive || keyguardManager.isDeviceLocked
+ val lowPriority = when {
+ isIncoming && isDeviceLocked -> false
+ !isIncoming && isDeviceLocked -> false
+ isIncoming && !isDeviceLocked -> config.alwaysShowFullscreen
+ else -> true
+ }
+
+ callNotificationManager.setupNotification(lowPriority)
+ if (
+ lowPriority
+ || !hasPermission(PERMISSION_POST_NOTIFICATIONS)
+ || !canUseFullScreenIntent()
+ ) {
try {
- callNotificationManager.setupNotification(true)
startActivity(CallActivity.getStartIntent(this))
- } catch (e: Exception) {
- // seems like startActivity can throw AndroidRuntimeException and ActivityNotFoundException, not yet sure when and why, lets show a notification
+ } catch (_: Exception) {
+ // seems like startActivity can throw AndroidRuntimeException and
+ // ActivityNotFoundException, not yet sure when and why, lets show a notification
callNotificationManager.setupNotification()
}
- } else {
- callNotificationManager.setupNotification()
}
}