diff --git a/app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt index 57f228df2..0ef5ba661 100644 --- a/app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt @@ -20,6 +20,7 @@ package org.linphone.compatibility import android.Manifest +import android.app.ActivityOptions import android.content.Context import android.content.pm.PackageManager import android.os.Build @@ -42,5 +43,11 @@ class Api33Compatibility { Manifest.permission.POST_NOTIFICATIONS ) == PackageManager.PERMISSION_GRANTED } + + fun getPendingIntentActivityOptions(): ActivityOptions { + val options = ActivityOptions.makeBasic() + options.isPendingIntentBackgroundActivityLaunchAllowed = true + return options + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt index 4bcdbc661..5d86ac7b7 100644 --- a/app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt @@ -19,12 +19,15 @@ */ package org.linphone.compatibility +import android.app.ActivityOptions import android.app.Notification import android.app.NotificationManager +import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent import android.os.Build +import android.os.Bundle import android.provider.Settings import androidx.annotation.RequiresApi import org.linphone.core.tools.Log @@ -73,5 +76,21 @@ class Api34Compatibility { Log.i("$TAG Starting ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT") context.startActivity(intent, null) } + + fun sendPendingIntent(pendingIntent: PendingIntent, bundle: Bundle) { + pendingIntent.send(bundle) + } + + fun getPendingIntentActivityOptions(creator: Boolean): ActivityOptions { + val options = ActivityOptions.makeBasic() + if (creator) { + options.pendingIntentCreatorBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + } else { + options.pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + } + return options + } } } diff --git a/app/src/main/java/org/linphone/compatibility/Api36Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api36Compatibility.kt new file mode 100644 index 000000000..d091c5964 --- /dev/null +++ b/app/src/main/java/org/linphone/compatibility/Api36Compatibility.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2010-2025 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.compatibility + +import android.app.ActivityOptions +import android.os.Build +import androidx.annotation.RequiresApi + +@RequiresApi(Build.VERSION_CODES.BAKLAVA) +class Api36Compatibility { + companion object { + private const val TAG = "[API 36 Compatibility]" + + fun getPendingIntentActivityOptions(creator: Boolean): ActivityOptions { + val options = ActivityOptions.makeBasic() + if (creator) { + options.pendingIntentCreatorBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS + } else { + options.pendingIntentBackgroundActivityStartMode = + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS + } + return options + } + } +} diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt index 153dcf00f..321aba5a6 100644 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ b/app/src/main/java/org/linphone/compatibility/Compatibility.kt @@ -22,11 +22,14 @@ package org.linphone.compatibility import android.Manifest import android.annotation.SuppressLint import android.app.Activity +import android.app.ActivityOptions import android.app.Notification +import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Bundle import android.os.Environment import android.util.Patterns import android.view.View @@ -194,5 +197,24 @@ class Compatibility { } return Patterns.IP_ADDRESS.matcher(string).matches() } + + fun sendPendingIntent(pendingIntent: PendingIntent, bundle: Bundle) { + if (Version.sdkAboveOrEqual(Version.API34_ANDROID_14_UPSIDE_DOWN_CAKE)) { + return Api34Compatibility.sendPendingIntent(pendingIntent, bundle) + } + pendingIntent.send() + } + + fun getPendingIntentActivityOptions(creator: Boolean): ActivityOptions { + if (Version.sdkAboveOrEqual(Version.API36_ANDROID_16_BAKLAVA)) { + return Api36Compatibility.getPendingIntentActivityOptions(creator) + } else if (Version.sdkAboveOrEqual(Version.API34_ANDROID_14_UPSIDE_DOWN_CAKE)) { + return Api34Compatibility.getPendingIntentActivityOptions(creator) + } else if (Version.sdkAboveOrEqual(Version.API33_ANDROID_13_TIRAMISU)) { + return Api33Compatibility.getPendingIntentActivityOptions() + } + + return ActivityOptions.makeBasic() + } } } diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 4b0380025..932841519 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -21,6 +21,7 @@ package org.linphone.core import android.annotation.SuppressLint import android.app.Application +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.media.AudioDeviceCallback @@ -1002,7 +1003,17 @@ class CoreContext intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT ) - context.startActivity(intent) + val options = Compatibility.getPendingIntentActivityOptions(true) + val pendingIntent = PendingIntent.getActivity( + context, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + options.toBundle() + ) + + val senderOptions = Compatibility.getPendingIntentActivityOptions(false) + Compatibility.sendPendingIntent(pendingIntent, senderOptions.toBundle()) } @WorkerThread diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt index 1f5bea5f2..0964df3d5 100644 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt @@ -90,6 +90,11 @@ class NotificationsManager const val INTENT_TOGGLE_SPEAKER_CALL_NOTIF_ACTION = "org.linphone.TOGGLE_SPEAKER_CALL_ACTION" const val INTENT_REPLY_MESSAGE_NOTIF_ACTION = "org.linphone.REPLY_ACTION" const val INTENT_MARK_MESSAGE_AS_READ_NOTIF_ACTION = "org.linphone.MARK_AS_READ_ACTION" + + const val INTENT_ANSWER_CALL_NOTIF_CODE = 2 + const val INTENT_HANGUP_CALL_NOTIF_CODE = 3 + const val INTENT_TOGGLE_SPEAKER_CALL_NOTIF_CODE = 4 + const val INTENT_NOTIF_ID = "NOTIFICATION_ID" const val KEY_TEXT_REPLY = "key_text_reply" @@ -658,18 +663,21 @@ class NotificationsManager val notifiable = getNotifiableForCall(call) val callNotificationIntent = Intent(context, CallActivity::class.java) - callNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + callNotificationIntent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT + ) if (isIncoming) { callNotificationIntent.putExtra("IncomingCall", true) } else { callNotificationIntent.putExtra("ActiveCall", true) } - + val options = Compatibility.getPendingIntentActivityOptions(true) val pendingIntent = PendingIntent.getActivity( context, 0, callNotificationIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + options.toBundle() ) val notification = createCallNotification( @@ -1497,13 +1505,15 @@ class NotificationsManager @AnyThread fun getCallDeclinePendingIntent(notifiable: Notifiable): PendingIntent { val hangupIntent = Intent(context, NotificationBroadcastReceiver::class.java) - hangupIntent.action = INTENT_HANGUP_CALL_NOTIF_ACTION - hangupIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) - hangupIntent.putExtra(INTENT_REMOTE_SIP_URI, notifiable.remoteAddress) + hangupIntent.apply { + action = INTENT_HANGUP_CALL_NOTIF_ACTION + putExtra(INTENT_NOTIF_ID, notifiable.notificationId) + putExtra(INTENT_REMOTE_SIP_URI, notifiable.remoteAddress) + } return PendingIntent.getBroadcast( context, - 3, + INTENT_HANGUP_CALL_NOTIF_CODE, hangupIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) @@ -1512,28 +1522,32 @@ class NotificationsManager @AnyThread fun getCallAnswerPendingIntent(notifiable: Notifiable): PendingIntent { val answerIntent = Intent(context, NotificationBroadcastReceiver::class.java) - answerIntent.action = INTENT_ANSWER_CALL_NOTIF_ACTION - answerIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) - answerIntent.putExtra(INTENT_REMOTE_SIP_URI, notifiable.remoteAddress) + answerIntent.apply { + action = INTENT_ANSWER_CALL_NOTIF_ACTION + putExtra(INTENT_NOTIF_ID, notifiable.notificationId) + putExtra(INTENT_REMOTE_SIP_URI, notifiable.remoteAddress) + } return PendingIntent.getBroadcast( context, - 2, + INTENT_ANSWER_CALL_NOTIF_CODE, answerIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, ) } @AnyThread fun getCallToggleSpeakerPendingIntent(notifiable: Notifiable): PendingIntent { val answerIntent = Intent(context, NotificationBroadcastReceiver::class.java) - answerIntent.action = INTENT_TOGGLE_SPEAKER_CALL_NOTIF_ACTION - answerIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) - answerIntent.putExtra(INTENT_REMOTE_SIP_URI, notifiable.remoteAddress) + answerIntent.apply { + action = INTENT_TOGGLE_SPEAKER_CALL_NOTIF_ACTION + putExtra(INTENT_NOTIF_ID, notifiable.notificationId) + putExtra(INTENT_REMOTE_SIP_URI, notifiable.remoteAddress) + } return PendingIntent.getBroadcast( context, - 4, + INTENT_TOGGLE_SPEAKER_CALL_NOTIF_CODE, answerIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) @@ -1577,10 +1591,12 @@ class NotificationsManager RemoteInput.Builder(KEY_TEXT_REPLY).setLabel(replyLabel).build() val replyIntent = Intent(context, NotificationBroadcastReceiver::class.java) - replyIntent.action = INTENT_REPLY_MESSAGE_NOTIF_ACTION - replyIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) - replyIntent.putExtra(INTENT_LOCAL_IDENTITY, notifiable.localIdentity) - replyIntent.putExtra(INTENT_REMOTE_SIP_URI, notifiable.remoteAddress) + replyIntent.apply { + action = INTENT_REPLY_MESSAGE_NOTIF_ACTION + putExtra(INTENT_NOTIF_ID, notifiable.notificationId) + putExtra(INTENT_LOCAL_IDENTITY, notifiable.localIdentity) + putExtra(INTENT_REMOTE_SIP_URI, notifiable.remoteAddress) + } // PendingIntents attached to actions with remote inputs must be mutable val replyPendingIntent = PendingIntent.getBroadcast( @@ -1604,10 +1620,12 @@ class NotificationsManager @AnyThread private fun getMarkMessageAsReadPendingIntent(notifiable: Notifiable): PendingIntent { val markAsReadIntent = Intent(context, NotificationBroadcastReceiver::class.java) - markAsReadIntent.action = INTENT_MARK_MESSAGE_AS_READ_NOTIF_ACTION - markAsReadIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) - markAsReadIntent.putExtra(INTENT_LOCAL_IDENTITY, notifiable.localIdentity) - markAsReadIntent.putExtra(INTENT_REMOTE_SIP_URI, notifiable.remoteAddress) + markAsReadIntent.apply { + action = INTENT_MARK_MESSAGE_AS_READ_NOTIF_ACTION + putExtra(INTENT_NOTIF_ID, notifiable.notificationId) + putExtra(INTENT_LOCAL_IDENTITY, notifiable.localIdentity) + putExtra(INTENT_REMOTE_SIP_URI, notifiable.remoteAddress) + } return PendingIntent.getBroadcast( context,