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() } }