fix: notification Accept opens CallActivity directly (Android 12+)
Android 12+ blocks starting activities from BroadcastReceivers used as notification trampolines. The Accept action now uses PendingIntent.getActivity to launch CallActivity directly with EXTRA_ACCEPT_CALL. CallActivity.onCreate/onNewIntent checks this extra and accepts the incoming call. The Reject action remains as a BroadcastReceiver since it doesn't need to start an activity. https://claude.ai/code/session_01Ak5tTkujpjNG1r5ASuPipZ
This commit is contained in:
+6
-3
@@ -561,13 +561,16 @@ object NotificationUtils {
|
|||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Accept launches CallActivity directly (not via BroadcastReceiver)
|
||||||
|
// to comply with Android 12+ notification trampoline restrictions.
|
||||||
val acceptIntent =
|
val acceptIntent =
|
||||||
Intent(applicationContext, com.vitorpamplona.amethyst.ui.call.CallNotificationReceiver::class.java).apply {
|
Intent(applicationContext, com.vitorpamplona.amethyst.ui.call.CallActivity::class.java).apply {
|
||||||
action = com.vitorpamplona.amethyst.ui.call.CallNotificationReceiver.ACTION_ACCEPT_CALL
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
|
putExtra(com.vitorpamplona.amethyst.ui.call.CallActivity.EXTRA_ACCEPT_CALL, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
val acceptPendingIntent =
|
val acceptPendingIntent =
|
||||||
PendingIntent.getBroadcast(
|
PendingIntent.getActivity(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
CALL_NOTIFICATION_ID + 1,
|
CALL_NOTIFICATION_ID + 1,
|
||||||
acceptIntent,
|
acceptIntent,
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ class CallActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
registerPipReceiver()
|
registerPipReceiver()
|
||||||
observeVideoStateForPip(callController)
|
observeVideoStateForPip(callController)
|
||||||
|
handleAcceptCallIntent(intent)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
AmethystTheme {
|
AmethystTheme {
|
||||||
@@ -111,6 +112,27 @@ class CallActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
handleAcceptCallIntent(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleAcceptCallIntent(intent: Intent?) {
|
||||||
|
if (intent?.getBooleanExtra(EXTRA_ACCEPT_CALL, false) != true) return
|
||||||
|
// Clear the extra so it doesn't re-trigger on config changes
|
||||||
|
intent.removeExtra(EXTRA_ACCEPT_CALL)
|
||||||
|
|
||||||
|
com.vitorpamplona.amethyst.service.notifications.NotificationUtils
|
||||||
|
.cancelCallNotification(this)
|
||||||
|
|
||||||
|
val callController = ActiveCallHolder.callController ?: return
|
||||||
|
val callManager = ActiveCallHolder.callManager ?: return
|
||||||
|
val state = callManager.state.value
|
||||||
|
if (state is CallState.IncomingCall) {
|
||||||
|
callController.acceptIncomingCall(state.sdpOffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onUserLeaveHint() {
|
override fun onUserLeaveHint() {
|
||||||
super.onUserLeaveHint()
|
super.onUserLeaveHint()
|
||||||
enterPipIfActive()
|
enterPipIfActive()
|
||||||
@@ -274,6 +296,7 @@ class CallActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val EXTRA_ACCEPT_CALL = "com.vitorpamplona.amethyst.EXTRA_ACCEPT_CALL"
|
||||||
private const val ACTION_PIP_HANGUP = "com.vitorpamplona.amethyst.PIP_HANGUP"
|
private const val ACTION_PIP_HANGUP = "com.vitorpamplona.amethyst.PIP_HANGUP"
|
||||||
private const val ACTION_PIP_TOGGLE_MUTE = "com.vitorpamplona.amethyst.PIP_TOGGLE_MUTE"
|
private const val ACTION_PIP_TOGGLE_MUTE = "com.vitorpamplona.amethyst.PIP_TOGGLE_MUTE"
|
||||||
private const val PIP_HANGUP_REQUEST_CODE = 0x60001
|
private const val PIP_HANGUP_REQUEST_CODE = 0x60001
|
||||||
|
|||||||
+6
-16
@@ -23,12 +23,17 @@ package com.vitorpamplona.amethyst.ui.call
|
|||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.vitorpamplona.amethyst.commons.call.CallState
|
|
||||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils
|
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the Reject action from the incoming call notification.
|
||||||
|
*
|
||||||
|
* The Accept action launches [CallActivity] directly via PendingIntent.getActivity
|
||||||
|
* to comply with Android 12+ notification trampoline restrictions.
|
||||||
|
*/
|
||||||
class CallNotificationReceiver : BroadcastReceiver() {
|
class CallNotificationReceiver : BroadcastReceiver() {
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
override fun onReceive(
|
override fun onReceive(
|
||||||
@@ -36,20 +41,6 @@ class CallNotificationReceiver : BroadcastReceiver() {
|
|||||||
intent: Intent,
|
intent: Intent,
|
||||||
) {
|
) {
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
ACTION_ACCEPT_CALL -> {
|
|
||||||
NotificationUtils.cancelCallNotification(context)
|
|
||||||
|
|
||||||
val callController = ActiveCallHolder.callController
|
|
||||||
val callManager = ActiveCallHolder.callManager
|
|
||||||
if (callController == null || callManager == null) return
|
|
||||||
|
|
||||||
val state = callManager.state.value
|
|
||||||
if (state is CallState.IncomingCall) {
|
|
||||||
callController.acceptIncomingCall(state.sdpOffer)
|
|
||||||
CallActivity.launch(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ACTION_REJECT_CALL -> {
|
ACTION_REJECT_CALL -> {
|
||||||
NotificationUtils.cancelCallNotification(context)
|
NotificationUtils.cancelCallNotification(context)
|
||||||
|
|
||||||
@@ -62,7 +53,6 @@ class CallNotificationReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_ACCEPT_CALL = "com.vitorpamplona.amethyst.ACCEPT_CALL"
|
|
||||||
const val ACTION_REJECT_CALL = "com.vitorpamplona.amethyst.REJECT_CALL"
|
const val ACTION_REJECT_CALL = "com.vitorpamplona.amethyst.REJECT_CALL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user