Improved NotificationsManager code, added call notification PendingIntent's ActivityOptions bundle, use PendingIntent in CoreContext to start CallActivity

This commit is contained in:
Sylvain Berfini 2025-06-18 12:00:01 +02:00
parent 60c74ee5b2
commit 42fbbc51fd
6 changed files with 145 additions and 25 deletions

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}
}

View file

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

View file

@ -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

View file

@ -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,