@capgo/capacitor-stream-call 0.0.87 → 0.0.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +2 -0
- package/android/src/main/java/ee/forgr/capacitor/streamcall/CustomStreamIntentResolver.kt +161 -0
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallPlugin.kt +57 -59
- package/package.json +1 -1
- package/android/src/main/java/ee/forgr/capacitor/streamcall/CustomNotificationHandler.kt +0 -323
package/android/build.gradle
CHANGED
|
@@ -73,6 +73,8 @@ dependencies {
|
|
|
73
73
|
implementation "androidx.compose.foundation:foundation:$compose_version"
|
|
74
74
|
implementation "androidx.compose.runtime:runtime:$compose_version"
|
|
75
75
|
implementation "androidx.compose.material3:material3:1.3.2"
|
|
76
|
+
implementation 'androidx.media:media:1.7.0'
|
|
77
|
+
|
|
76
78
|
|
|
77
79
|
// Stream dependencies
|
|
78
80
|
implementation("io.getstream:stream-video-android-ui-compose:1.9.2")
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
package ee.forgr.capacitor.streamcall
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import android.app.PendingIntent
|
|
5
|
+
import android.content.ComponentName
|
|
6
|
+
import android.content.Intent
|
|
7
|
+
import android.content.pm.ResolveInfo
|
|
8
|
+
import io.getstream.log.taggedLogger
|
|
9
|
+
import io.getstream.video.android.core.notifications.NotificationHandler
|
|
10
|
+
import io.getstream.video.android.core.notifications.StreamIntentResolver
|
|
11
|
+
import io.getstream.video.android.model.StreamCallId
|
|
12
|
+
|
|
13
|
+
class CustomStreamIntentResolver(private val context: Application) : StreamIntentResolver {
|
|
14
|
+
|
|
15
|
+
private val logger by taggedLogger("CustomIntentResolver")
|
|
16
|
+
private val PENDING_INTENT_FLAG = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
17
|
+
|
|
18
|
+
override fun searchIncomingCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
|
|
19
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
|
20
|
+
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
|
21
|
+
putExtra("callCid", callId.cid)
|
|
22
|
+
putExtra("action", "accept")
|
|
23
|
+
action = "io.getstream.video.android.action.ACCEPT_CALL"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return PendingIntent.getActivity(
|
|
27
|
+
context,
|
|
28
|
+
callId.cid.hashCode(),
|
|
29
|
+
launchIntent,
|
|
30
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun searchOutgoingCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
|
|
35
|
+
// For outgoing calls, create a specific intent that only opens webview when user taps
|
|
36
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
|
37
|
+
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
|
38
|
+
putExtra("callCid", callId.cid)
|
|
39
|
+
putExtra("action", "outgoing_call_tap") // Different action to distinguish from automatic events
|
|
40
|
+
putExtra("openWebview", true)
|
|
41
|
+
putExtra("fromNotification", true)
|
|
42
|
+
putExtra("userTapped", true) // Explicitly mark this as user-initiated
|
|
43
|
+
action = "ee.forgr.capacitor.streamcall.OUTGOING_CALL_TAP.${callId.cid}"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return PendingIntent.getActivity(
|
|
47
|
+
context,
|
|
48
|
+
callId.cid.hashCode(),
|
|
49
|
+
launchIntent,
|
|
50
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun searchNotificationCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? =
|
|
55
|
+
searchActivityPendingIntent(Intent(NotificationHandler.ACTION_NOTIFICATION), callId, notificationId)
|
|
56
|
+
|
|
57
|
+
override fun searchMissedCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? =
|
|
58
|
+
searchActivityPendingIntent(Intent(NotificationHandler.ACTION_MISSED_CALL), callId, notificationId)
|
|
59
|
+
|
|
60
|
+
override fun getDefaultPendingIntent(): PendingIntent {
|
|
61
|
+
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
|
62
|
+
?: Intent(Intent.ACTION_MAIN).apply {
|
|
63
|
+
setPackage(context.packageName)
|
|
64
|
+
addCategory(Intent.CATEGORY_LAUNCHER)
|
|
65
|
+
}
|
|
66
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
67
|
+
return PendingIntent.getActivity(
|
|
68
|
+
context,
|
|
69
|
+
0,
|
|
70
|
+
intent,
|
|
71
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override fun searchLiveCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? =
|
|
76
|
+
searchActivityPendingIntent(Intent(NotificationHandler.ACTION_LIVE_CALL), callId, notificationId)
|
|
77
|
+
|
|
78
|
+
override fun searchAcceptCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
|
|
79
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
|
80
|
+
action = NotificationHandler.ACTION_ACCEPT_CALL
|
|
81
|
+
putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
82
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return PendingIntent.getActivity(
|
|
86
|
+
context,
|
|
87
|
+
callId.cid.hashCode() + 10,
|
|
88
|
+
launchIntent,
|
|
89
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override fun searchRejectCallPendingIntent(callId: StreamCallId): PendingIntent? =
|
|
94
|
+
searchBroadcastPendingIntent(Intent(NotificationHandler.ACTION_REJECT_CALL), callId)
|
|
95
|
+
|
|
96
|
+
override fun searchEndCallPendingIntent(callId: StreamCallId): PendingIntent? {
|
|
97
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
|
98
|
+
action = NotificationHandler.ACTION_LEAVE_CALL
|
|
99
|
+
putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
100
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return PendingIntent.getActivity(
|
|
104
|
+
context,
|
|
105
|
+
callId.cid.hashCode() + 30,
|
|
106
|
+
launchIntent,
|
|
107
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
override fun searchOngoingCallPendingIntent(callId: StreamCallId, notificationId: Int): PendingIntent? {
|
|
112
|
+
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
|
|
113
|
+
action = NotificationHandler.ACTION_ONGOING_CALL
|
|
114
|
+
putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
115
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return PendingIntent.getActivity(
|
|
119
|
+
context,
|
|
120
|
+
callId.cid.hashCode() + 20,
|
|
121
|
+
launchIntent,
|
|
122
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private fun searchBroadcastPendingIntent(baseIntent: Intent, callId: StreamCallId): PendingIntent? =
|
|
127
|
+
searchResolveInfo { context.packageManager.queryBroadcastReceivers(baseIntent, 0) }?.let {
|
|
128
|
+
getBroadcastForIntent(baseIntent, it, callId)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun searchActivityPendingIntent(baseIntent: Intent, callId: StreamCallId, notificationId: Int): PendingIntent? =
|
|
132
|
+
searchResolveInfo { context.packageManager.queryIntentActivities(baseIntent, 0) }?.let {
|
|
133
|
+
getActivityForIntent(baseIntent, it, callId, notificationId)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private fun searchResolveInfo(availableComponents: () -> List<ResolveInfo>): ResolveInfo? =
|
|
137
|
+
availableComponents()
|
|
138
|
+
.filter { it.activityInfo.packageName == context.packageName }
|
|
139
|
+
.maxByOrNull { it.priority }
|
|
140
|
+
|
|
141
|
+
private fun getActivityForIntent(baseIntent: Intent, resolveInfo: ResolveInfo, callId: StreamCallId, notificationId: Int, flags: Int = PENDING_INTENT_FLAG): PendingIntent {
|
|
142
|
+
return PendingIntent.getActivity(
|
|
143
|
+
context,
|
|
144
|
+
notificationId,
|
|
145
|
+
buildComponentIntent(baseIntent, resolveInfo, callId),
|
|
146
|
+
flags
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private fun getBroadcastForIntent(baseIntent: Intent, resolveInfo: ResolveInfo, callId: StreamCallId, flags: Int = PENDING_INTENT_FLAG): PendingIntent {
|
|
151
|
+
return PendingIntent.getBroadcast(context, 0, buildComponentIntent(baseIntent, resolveInfo, callId), flags)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private fun buildComponentIntent(baseIntent: Intent, resolveInfo: ResolveInfo, callId: StreamCallId): Intent {
|
|
155
|
+
return Intent(baseIntent).apply {
|
|
156
|
+
component = ComponentName(resolveInfo.activityInfo.applicationInfo.packageName, resolveInfo.activityInfo.name)
|
|
157
|
+
putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
158
|
+
putExtra(NotificationHandler.INTENT_EXTRA_NOTIFICATION_ID, NotificationHandler.INCOMING_CALL_NOTIFICATION_ID)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -73,6 +73,10 @@ import io.getstream.video.android.core.events.ParticipantLeftEvent
|
|
|
73
73
|
import io.getstream.video.android.core.internal.InternalStreamVideoApi
|
|
74
74
|
import io.getstream.video.android.core.notifications.NotificationConfig
|
|
75
75
|
import io.getstream.video.android.core.notifications.NotificationHandler
|
|
76
|
+
import io.getstream.video.android.core.notifications.handlers.CompatibilityStreamNotificationHandler
|
|
77
|
+
import io.getstream.video.android.core.notifications.handlers.StreamNotificationBuilderInterceptors
|
|
78
|
+
import androidx.core.app.NotificationCompat
|
|
79
|
+
import android.app.PendingIntent
|
|
76
80
|
import io.getstream.video.android.core.sounds.RingingConfig
|
|
77
81
|
import io.getstream.video.android.core.sounds.toSounds
|
|
78
82
|
import io.getstream.video.android.model.Device
|
|
@@ -316,6 +320,12 @@ class StreamCallPlugin : Plugin() {
|
|
|
316
320
|
Log.e("StreamCallPlugin", "handleOnNewIntent: ACCEPT_CALL - Call object is null for cid: $cid")
|
|
317
321
|
}
|
|
318
322
|
}
|
|
323
|
+
} else if (action === NotificationHandler.ACTION_LEAVE_CALL) {
|
|
324
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: Matched LEAVE_CALL action")
|
|
325
|
+
kotlinx.coroutines.GlobalScope.launch {
|
|
326
|
+
val success = _endCall()
|
|
327
|
+
Log.d("StreamCallPlugin", "handleOnNewIntent: LEAVE_CALL - End call result: $success")
|
|
328
|
+
}
|
|
319
329
|
}
|
|
320
330
|
// Log the intent information
|
|
321
331
|
Log.d("StreamCallPlugin", "New Intent - Action: $action")
|
|
@@ -567,47 +577,6 @@ class StreamCallPlugin : Plugin() {
|
|
|
567
577
|
// unsafe cast, add better handling
|
|
568
578
|
val application = contextToUse.applicationContext as Application
|
|
569
579
|
Log.d("StreamCallPlugin", "No existing StreamVideo singleton client, creating new one")
|
|
570
|
-
val notificationHandler = CustomNotificationHandler(
|
|
571
|
-
application = application,
|
|
572
|
-
endCall = { callId ->
|
|
573
|
-
val activeCall = streamVideoClient?.call(callId.type, callId.id)
|
|
574
|
-
|
|
575
|
-
kotlinx.coroutines.GlobalScope.launch {
|
|
576
|
-
try {
|
|
577
|
-
Log.i(
|
|
578
|
-
"StreamCallPlugin",
|
|
579
|
-
"Attempt to endCallRaw, activeCall == null: ${activeCall == null}",
|
|
580
|
-
)
|
|
581
|
-
activeCall?.let { endCallRaw(it) }
|
|
582
|
-
} catch (e: Exception) {
|
|
583
|
-
Log.e(
|
|
584
|
-
"StreamCallPlugin",
|
|
585
|
-
"Error ending after missed call notif action",
|
|
586
|
-
e
|
|
587
|
-
)
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
},
|
|
591
|
-
incomingCall = {
|
|
592
|
-
if (this.savedContext != null && initializationTime != 0L) {
|
|
593
|
-
val contextCreatedAt = initializationTime
|
|
594
|
-
val now = System.currentTimeMillis()
|
|
595
|
-
val isWithinOneSecond = (now - contextCreatedAt) <= 1000L
|
|
596
|
-
|
|
597
|
-
Log.i(
|
|
598
|
-
"StreamCallPlugin",
|
|
599
|
-
"Time between context creation and activity created (incoming call notif): ${now - contextCreatedAt}"
|
|
600
|
-
)
|
|
601
|
-
if (isWithinOneSecond && !bootedToHandleCall) {
|
|
602
|
-
Log.i(
|
|
603
|
-
"StreamCallPlugin",
|
|
604
|
-
"Notification incomingCall received less than 1 second after the creation of streamVideoSDK. Booted FOR SURE in order to handle the notification"
|
|
605
|
-
)
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
)
|
|
610
|
-
|
|
611
580
|
val notificationConfig = NotificationConfig(
|
|
612
581
|
pushDeviceGenerators = listOf(
|
|
613
582
|
FirebasePushDeviceGenerator(
|
|
@@ -616,7 +585,23 @@ class StreamCallPlugin : Plugin() {
|
|
|
616
585
|
)
|
|
617
586
|
),
|
|
618
587
|
requestPermissionOnAppLaunch = { true },
|
|
619
|
-
notificationHandler =
|
|
588
|
+
notificationHandler = CompatibilityStreamNotificationHandler(
|
|
589
|
+
application = contextToUse as Application,
|
|
590
|
+
intentResolver = CustomStreamIntentResolver(contextToUse),
|
|
591
|
+
initialNotificationBuilderInterceptor = object : StreamNotificationBuilderInterceptors() {
|
|
592
|
+
override fun onBuildIncomingCallNotification(
|
|
593
|
+
builder: NotificationCompat.Builder,
|
|
594
|
+
fullScreenPendingIntent: PendingIntent,
|
|
595
|
+
acceptCallPendingIntent: PendingIntent,
|
|
596
|
+
rejectCallPendingIntent: PendingIntent,
|
|
597
|
+
callerName: String?,
|
|
598
|
+
shouldHaveContentIntent: Boolean
|
|
599
|
+
): NotificationCompat.Builder {
|
|
600
|
+
return builder.setContentIntent(fullScreenPendingIntent)
|
|
601
|
+
.setFullScreenIntent(fullScreenPendingIntent, true)
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
)
|
|
620
605
|
)
|
|
621
606
|
|
|
622
607
|
val soundsConfig = incomingOnlyRingingConfig()
|
|
@@ -1976,28 +1961,41 @@ class StreamCallPlugin : Plugin() {
|
|
|
1976
1961
|
}
|
|
1977
1962
|
|
|
1978
1963
|
@OptIn(DelicateCoroutinesApi::class)
|
|
1964
|
+
private suspend fun _endCall(): Boolean {
|
|
1965
|
+
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1966
|
+
val ringingCall = streamVideoClient?.state?.ringingCall?.value
|
|
1967
|
+
|
|
1968
|
+
val callToEnd = activeCall ?: ringingCall
|
|
1969
|
+
|
|
1970
|
+
if (callToEnd == null) {
|
|
1971
|
+
Log.w("StreamCallPlugin", "Attempted to end call but no active or ringing call found")
|
|
1972
|
+
return false
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
Log.d("StreamCallPlugin", "Ending call: activeCall=${activeCall?.id}, ringingCall=${ringingCall?.id}, callToEnd=${callToEnd.id}")
|
|
1976
|
+
|
|
1977
|
+
return try {
|
|
1978
|
+
endCallRaw(callToEnd)
|
|
1979
|
+
true
|
|
1980
|
+
} catch (e: Exception) {
|
|
1981
|
+
Log.e("StreamCallPlugin", "Error ending call: ${e.message}", e)
|
|
1982
|
+
false
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1979
1986
|
@PluginMethod
|
|
1980
1987
|
fun endCall(call: PluginCall) {
|
|
1981
1988
|
try {
|
|
1982
|
-
val activeCall = streamVideoClient?.state?.activeCall?.value
|
|
1983
|
-
val ringingCall = streamVideoClient?.state?.ringingCall?.value
|
|
1984
|
-
|
|
1985
|
-
val callToEnd = activeCall ?: ringingCall
|
|
1986
|
-
|
|
1987
|
-
if (callToEnd == null) {
|
|
1988
|
-
Log.w("StreamCallPlugin", "Attempted to end call but no active or ringing call found")
|
|
1989
|
-
call.reject("No active call to end")
|
|
1990
|
-
return
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
Log.d("StreamCallPlugin", "Ending call: activeCall=${activeCall?.id}, ringingCall=${ringingCall?.id}, callToEnd=${callToEnd.id}")
|
|
1994
|
-
|
|
1995
1989
|
kotlinx.coroutines.GlobalScope.launch {
|
|
1996
1990
|
try {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
1991
|
+
val success = _endCall()
|
|
1992
|
+
if (success) {
|
|
1993
|
+
call.resolve(JSObject().apply {
|
|
1994
|
+
put("success", true)
|
|
1995
|
+
})
|
|
1996
|
+
} else {
|
|
1997
|
+
call.reject("No active call to end")
|
|
1998
|
+
}
|
|
2001
1999
|
} catch (e: Exception) {
|
|
2002
2000
|
Log.e("StreamCallPlugin", "Error ending call: ${e.message}")
|
|
2003
2001
|
call.reject("Failed to end call: ${e.message}")
|
package/package.json
CHANGED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
package ee.forgr.capacitor.streamcall
|
|
2
|
-
|
|
3
|
-
import android.app.Application
|
|
4
|
-
import android.app.Notification
|
|
5
|
-
import android.app.NotificationManager
|
|
6
|
-
import android.app.PendingIntent
|
|
7
|
-
import android.content.Intent
|
|
8
|
-
import android.media.RingtoneManager
|
|
9
|
-
import android.os.Build
|
|
10
|
-
import android.util.Log
|
|
11
|
-
import androidx.core.app.NotificationCompat
|
|
12
|
-
import io.getstream.video.android.core.RingingState
|
|
13
|
-
import io.getstream.video.android.core.notifications.DefaultNotificationHandler
|
|
14
|
-
import io.getstream.video.android.core.notifications.NotificationHandler
|
|
15
|
-
import io.getstream.video.android.model.StreamCallId
|
|
16
|
-
import io.getstream.video.android.model.streamCallId
|
|
17
|
-
import io.getstream.video.android.core.R
|
|
18
|
-
|
|
19
|
-
// declare "incoming_calls_custom" as a constant
|
|
20
|
-
const val INCOMING_CALLS_CUSTOM = "incoming_calls_custom"
|
|
21
|
-
|
|
22
|
-
class CustomNotificationHandler(
|
|
23
|
-
val application: Application,
|
|
24
|
-
private val endCall: (callId: StreamCallId) -> Unit = {},
|
|
25
|
-
private val incomingCall: () -> Unit = {}
|
|
26
|
-
) : DefaultNotificationHandler(application, hideRingingNotificationInForeground = false) {
|
|
27
|
-
companion object {
|
|
28
|
-
private const val PREFS_NAME = "StreamCallPrefs"
|
|
29
|
-
private const val KEY_NOTIFICATION_TIME = "notification_creation_time"
|
|
30
|
-
}
|
|
31
|
-
private var allowSound = true
|
|
32
|
-
|
|
33
|
-
override fun getRingingCallNotification(
|
|
34
|
-
ringingState: RingingState,
|
|
35
|
-
callId: StreamCallId,
|
|
36
|
-
callDisplayName: String?,
|
|
37
|
-
shouldHaveContentIntent: Boolean,
|
|
38
|
-
): Notification? {
|
|
39
|
-
Log.d("CustomNotificationHandler", "getRingingCallNotification called: ringingState=$ringingState, callId=$callId, callDisplayName=$callDisplayName, shouldHaveContentIntent=$shouldHaveContentIntent")
|
|
40
|
-
return if (ringingState is RingingState.Incoming) {
|
|
41
|
-
// Note: we create our own fullScreenPendingIntent later based on acceptCallPendingIntent
|
|
42
|
-
|
|
43
|
-
// Get the main launch intent for the application
|
|
44
|
-
val launchIntent = application.packageManager.getLaunchIntentForPackage(application.packageName)
|
|
45
|
-
var targetComponent: android.content.ComponentName? = null
|
|
46
|
-
if (launchIntent != null) {
|
|
47
|
-
targetComponent = launchIntent.component
|
|
48
|
-
Log.d("CustomNotificationHandler", "Derived launch component: ${targetComponent?.flattenToString()}")
|
|
49
|
-
} else {
|
|
50
|
-
Log.e("CustomNotificationHandler", "Could not get launch intent for package: ${application.packageName}. This is problematic for creating explicit intents.")
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Intent to simply bring the app to foreground and show incoming-call UI (no auto accept)
|
|
54
|
-
val incomingIntentAction = "io.getstream.video.android.action.INCOMING_CALL"
|
|
55
|
-
val incomingCallIntent = Intent(incomingIntentAction)
|
|
56
|
-
.putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
57
|
-
.setPackage(application.packageName)
|
|
58
|
-
if (targetComponent != null) incomingCallIntent.component = targetComponent
|
|
59
|
-
incomingCallIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
60
|
-
|
|
61
|
-
// Use the app's MainActivity intent so webview loads; user sees app UI
|
|
62
|
-
val requestCodeFull = callId.cid.hashCode()
|
|
63
|
-
val fullScreenPendingIntent = PendingIntent.getActivity(
|
|
64
|
-
application,
|
|
65
|
-
requestCodeFull,
|
|
66
|
-
incomingCallIntent,
|
|
67
|
-
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
val acceptCallAction = NotificationHandler.ACTION_ACCEPT_CALL
|
|
71
|
-
val acceptCallIntent = Intent(acceptCallAction).apply {
|
|
72
|
-
putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
73
|
-
// Explicitly target our manifest-declared receiver
|
|
74
|
-
setClass(application, AcceptCallReceiver::class.java)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
Log.d("CustomNotificationHandler", "Constructed Accept Call Intent for PI: action=${acceptCallIntent.action}, cid=${acceptCallIntent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)}, component=${acceptCallIntent.component}")
|
|
78
|
-
|
|
79
|
-
val requestCodeAccept = callId.cid.hashCode() + 1 // Unique request code for the PendingIntent with offset to avoid collisions
|
|
80
|
-
val acceptCallPendingIntent = createAcceptCallPendingIntent(callId, requestCodeAccept, acceptCallIntent)
|
|
81
|
-
Log.d("CustomNotificationHandler", "Created Accept Call PendingIntent with requestCode: $requestCodeAccept")
|
|
82
|
-
|
|
83
|
-
val rejectCallPendingIntent = intentResolver.searchRejectCallPendingIntent(callId) // Keep using resolver for reject for now, or change it too if needed
|
|
84
|
-
|
|
85
|
-
Log.d("CustomNotificationHandler", "Full Screen PI: $fullScreenPendingIntent")
|
|
86
|
-
Log.d("CustomNotificationHandler", "Custom Accept Call PI: $acceptCallPendingIntent")
|
|
87
|
-
Log.d("CustomNotificationHandler", "Resolver Reject Call PI: $rejectCallPendingIntent")
|
|
88
|
-
|
|
89
|
-
if (fullScreenPendingIntent != null && acceptCallPendingIntent != null && rejectCallPendingIntent != null) {
|
|
90
|
-
customGetIncomingCallNotification(
|
|
91
|
-
fullScreenPendingIntent,
|
|
92
|
-
acceptCallPendingIntent,
|
|
93
|
-
rejectCallPendingIntent,
|
|
94
|
-
callDisplayName,
|
|
95
|
-
shouldHaveContentIntent,
|
|
96
|
-
callId
|
|
97
|
-
)
|
|
98
|
-
} else {
|
|
99
|
-
Log.e("CustomNotificationHandler", "Ringing call notification not shown, one of the intents is null.")
|
|
100
|
-
null
|
|
101
|
-
}
|
|
102
|
-
} else if (ringingState is RingingState.Outgoing) {
|
|
103
|
-
val outgoingCallPendingIntent = intentResolver.searchOutgoingCallPendingIntent(callId)
|
|
104
|
-
val endCallPendingIntent = intentResolver.searchEndCallPendingIntent(callId)
|
|
105
|
-
|
|
106
|
-
if (outgoingCallPendingIntent != null && endCallPendingIntent != null) {
|
|
107
|
-
getOngoingCallNotification(
|
|
108
|
-
callId,
|
|
109
|
-
callDisplayName,
|
|
110
|
-
isOutgoingCall = true,
|
|
111
|
-
)
|
|
112
|
-
} else {
|
|
113
|
-
Log.e("CustomNotificationHandler", "Ringing call notification not shown, one of the intents is null.")
|
|
114
|
-
null
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
null
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private fun createAcceptCallPendingIntent(
|
|
122
|
-
callId: StreamCallId,
|
|
123
|
-
requestCode: Int,
|
|
124
|
-
acceptCallIntent: Intent
|
|
125
|
-
): PendingIntent? {
|
|
126
|
-
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
127
|
-
val launchIntent = application.packageManager.getLaunchIntentForPackage(application.packageName)
|
|
128
|
-
if (launchIntent != null) {
|
|
129
|
-
launchIntent.action = NotificationHandler.ACTION_ACCEPT_CALL
|
|
130
|
-
launchIntent.putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
131
|
-
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
|
132
|
-
PendingIntent.getActivity(
|
|
133
|
-
application,
|
|
134
|
-
requestCode,
|
|
135
|
-
launchIntent,
|
|
136
|
-
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
137
|
-
)
|
|
138
|
-
} else {
|
|
139
|
-
Log.e("CustomNotificationHandler", "Could not get launch intent for package to create Accept PI.")
|
|
140
|
-
null
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
PendingIntent.getBroadcast(
|
|
144
|
-
application,
|
|
145
|
-
requestCode,
|
|
146
|
-
acceptCallIntent,
|
|
147
|
-
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
148
|
-
)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
fun customGetIncomingCallNotification(
|
|
153
|
-
fullScreenPendingIntent: PendingIntent,
|
|
154
|
-
acceptCallPendingIntent: PendingIntent,
|
|
155
|
-
rejectCallPendingIntent: PendingIntent,
|
|
156
|
-
callerName: String?,
|
|
157
|
-
shouldHaveContentIntent: Boolean,
|
|
158
|
-
callId: StreamCallId
|
|
159
|
-
): Notification {
|
|
160
|
-
Log.d("CustomNotificationHandler", "customGetIncomingCallNotification called: callerName=$callerName, callId=$callId")
|
|
161
|
-
customCreateIncomingCallChannel()
|
|
162
|
-
// Always use the provided acceptCallPendingIntent (created with getActivity) so that
|
|
163
|
-
// the app process is started and MainActivity receives the ACCEPT_CALL action even
|
|
164
|
-
// when the app has been killed.
|
|
165
|
-
return buildNotification(
|
|
166
|
-
fullScreenPendingIntent,
|
|
167
|
-
acceptCallPendingIntent,
|
|
168
|
-
rejectCallPendingIntent,
|
|
169
|
-
callerName,
|
|
170
|
-
shouldHaveContentIntent,
|
|
171
|
-
INCOMING_CALLS_CUSTOM,
|
|
172
|
-
true // Include sound
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
private fun buildNotification(
|
|
177
|
-
fullScreenPendingIntent: PendingIntent,
|
|
178
|
-
acceptCallPendingIntent: PendingIntent,
|
|
179
|
-
rejectCallPendingIntent: PendingIntent,
|
|
180
|
-
callerName: String?,
|
|
181
|
-
shouldHaveContentIntent: Boolean,
|
|
182
|
-
channelId: String,
|
|
183
|
-
includeSound: Boolean
|
|
184
|
-
): Notification {
|
|
185
|
-
Log.d("CustomNotificationHandler", "buildNotification called: callerName=$callerName, channelId=$channelId, includeSound=$includeSound")
|
|
186
|
-
return getNotification {
|
|
187
|
-
priority = NotificationCompat.PRIORITY_HIGH
|
|
188
|
-
setContentTitle(callerName)
|
|
189
|
-
setContentText("Incoming call")
|
|
190
|
-
setChannelId(channelId)
|
|
191
|
-
setOngoing(true)
|
|
192
|
-
setAutoCancel(false)
|
|
193
|
-
setCategory(NotificationCompat.CATEGORY_CALL)
|
|
194
|
-
|
|
195
|
-
// Clear all defaults first
|
|
196
|
-
setDefaults(0)
|
|
197
|
-
|
|
198
|
-
if (includeSound && allowSound) {
|
|
199
|
-
setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))
|
|
200
|
-
setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_LIGHTS)
|
|
201
|
-
} else {
|
|
202
|
-
setSound(null)
|
|
203
|
-
setDefaults(NotificationCompat.DEFAULT_VIBRATE or NotificationCompat.DEFAULT_LIGHTS)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// setVibrate(longArrayOf(0, 1000, 500, 1000))
|
|
207
|
-
setLights(0xFF0000FF.toInt(), 1000, 1000)
|
|
208
|
-
setFullScreenIntent(fullScreenPendingIntent, true)
|
|
209
|
-
if (shouldHaveContentIntent) {
|
|
210
|
-
setContentIntent(fullScreenPendingIntent)
|
|
211
|
-
} else {
|
|
212
|
-
val emptyIntent = PendingIntent.getActivity(
|
|
213
|
-
application,
|
|
214
|
-
0,
|
|
215
|
-
Intent(),
|
|
216
|
-
PendingIntent.FLAG_IMMUTABLE,
|
|
217
|
-
)
|
|
218
|
-
setContentIntent(emptyIntent)
|
|
219
|
-
}
|
|
220
|
-
addCallActions(acceptCallPendingIntent, rejectCallPendingIntent, callerName)
|
|
221
|
-
}.apply {
|
|
222
|
-
// flags = flags or NotificationCompat.FLAG_ONGOING_EVENT
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
override fun onMissedCall(callId: StreamCallId, callDisplayName: String) {
|
|
227
|
-
Log.d("CustomNotificationHandler", "onMissedCall called: callId=$callId, callDisplayName=$callDisplayName")
|
|
228
|
-
endCall(callId)
|
|
229
|
-
super.onMissedCall(callId, callDisplayName)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
override fun getOngoingCallNotification(
|
|
233
|
-
callId: StreamCallId,
|
|
234
|
-
callDisplayName: String?,
|
|
235
|
-
isOutgoingCall: Boolean,
|
|
236
|
-
remoteParticipantCount: Int
|
|
237
|
-
): Notification? {
|
|
238
|
-
Log.d("CustomNotificationHandler", "getOngoingCallNotification called: callId=$callId, isOutgoing=$isOutgoingCall, participants=$remoteParticipantCount")
|
|
239
|
-
createOngoingCallChannel()
|
|
240
|
-
|
|
241
|
-
val launchIntent = application.packageManager.getLaunchIntentForPackage(application.packageName)
|
|
242
|
-
val contentIntent = if (launchIntent != null) {
|
|
243
|
-
launchIntent.putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
|
|
244
|
-
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
245
|
-
PendingIntent.getActivity(
|
|
246
|
-
application,
|
|
247
|
-
callId.cid.hashCode(),
|
|
248
|
-
launchIntent,
|
|
249
|
-
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
250
|
-
)
|
|
251
|
-
} else {
|
|
252
|
-
Log.e("CustomNotificationHandler", "Could not get launch intent for package: ${application.packageName}. Ongoing call notification will not open the app.")
|
|
253
|
-
null
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return getNotification {
|
|
257
|
-
setContentTitle(callDisplayName ?: "Ongoing Call")
|
|
258
|
-
setContentText("Tap to return to the call")
|
|
259
|
-
setSmallIcon(R.drawable.stream_video_ic_call)
|
|
260
|
-
setChannelId("ongoing_calls")
|
|
261
|
-
setOngoing(true)
|
|
262
|
-
setAutoCancel(false)
|
|
263
|
-
setCategory(NotificationCompat.CATEGORY_CALL)
|
|
264
|
-
setDefaults(0)
|
|
265
|
-
if (contentIntent != null) {
|
|
266
|
-
setContentIntent(contentIntent)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
private fun customCreateIncomingCallChannel() {
|
|
272
|
-
Log.d("CustomNotificationHandler", "customCreateIncomingCallChannel called")
|
|
273
|
-
maybeCreateChannel(
|
|
274
|
-
channelId = INCOMING_CALLS_CUSTOM,
|
|
275
|
-
context = application,
|
|
276
|
-
configure = {
|
|
277
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
278
|
-
name = application.getString(
|
|
279
|
-
R.string.stream_video_incoming_call_notification_channel_title,
|
|
280
|
-
)
|
|
281
|
-
description = application.getString(R.string.stream_video_incoming_call_notification_channel_description)
|
|
282
|
-
importance = NotificationManager.IMPORTANCE_HIGH
|
|
283
|
-
this.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
284
|
-
this.setShowBadge(true)
|
|
285
|
-
|
|
286
|
-
// Set the channel to be silent since we handle sound via RingtonePlayer
|
|
287
|
-
setSound(null, null)
|
|
288
|
-
enableVibration(true)
|
|
289
|
-
enableLights(true)
|
|
290
|
-
}
|
|
291
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
292
|
-
this.setAllowBubbles(true)
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private fun createOngoingCallChannel() {
|
|
299
|
-
Log.d("CustomNotificationHandler", "createOngoingCallChannel called")
|
|
300
|
-
maybeCreateChannel(
|
|
301
|
-
channelId = "ongoing_calls",
|
|
302
|
-
context = application,
|
|
303
|
-
configure = {
|
|
304
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
305
|
-
name = "Ongoing calls"
|
|
306
|
-
description = "Notifications for ongoing calls"
|
|
307
|
-
importance = NotificationManager.IMPORTANCE_LOW
|
|
308
|
-
this.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
|
309
|
-
this.setShowBadge(false)
|
|
310
|
-
setSound(null, null)
|
|
311
|
-
enableVibration(false)
|
|
312
|
-
enableLights(false)
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
)
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
fun clone(): CustomNotificationHandler {
|
|
319
|
-
Log.d("CustomNotificationHandler", "clone called")
|
|
320
|
-
return CustomNotificationHandler(this.application, this.endCall, this.incomingCall)
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|