@capgo/capacitor-stream-call 0.0.56 → 0.0.62
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 -2
- package/android/src/main/java/ee/forgr/capacitor/streamcall/CustomNotificationHandler.kt +4 -3
- package/android/src/main/java/ee/forgr/capacitor/streamcall/RingtonePlayer.kt +109 -109
- package/android/src/main/java/ee/forgr/capacitor/streamcall/StreamCallPlugin.kt +75 -90
- package/dist/esm/web.js +9 -7
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +9 -7
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +9 -7
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/StreamCallPlugin/TouchInterceptView.swift +5 -1
- package/package.json +1 -1
package/android/build.gradle
CHANGED
|
@@ -75,8 +75,8 @@ dependencies {
|
|
|
75
75
|
implementation "androidx.compose.material3:material3:1.3.2"
|
|
76
76
|
|
|
77
77
|
// Stream dependencies
|
|
78
|
-
implementation("io.getstream:stream-video-android-ui-compose:1.6.
|
|
79
|
-
implementation("io.getstream:stream-video-android-core:1.6.
|
|
78
|
+
implementation("io.getstream:stream-video-android-ui-compose:1.6.3")
|
|
79
|
+
implementation("io.getstream:stream-video-android-core:1.6.3")
|
|
80
80
|
implementation("io.getstream:stream-android-push:1.3.1")
|
|
81
81
|
implementation("io.getstream:stream-android-push-firebase:1.3.1")
|
|
82
82
|
|
|
@@ -13,7 +13,8 @@ import io.getstream.video.android.core.RingingState
|
|
|
13
13
|
import io.getstream.video.android.core.notifications.DefaultNotificationHandler
|
|
14
14
|
import io.getstream.video.android.core.notifications.NotificationHandler
|
|
15
15
|
import io.getstream.video.android.model.StreamCallId
|
|
16
|
-
|
|
16
|
+
import io.getstream.video.android.model.streamCallId
|
|
17
|
+
|
|
17
18
|
// declare "incoming_calls_custom" as a constant
|
|
18
19
|
const val INCOMING_CALLS_CUSTOM = "incoming_calls_custom"
|
|
19
20
|
|
|
@@ -76,7 +77,7 @@ class CustomNotificationHandler(
|
|
|
76
77
|
}
|
|
77
78
|
acceptCallIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
78
79
|
|
|
79
|
-
Log.d("CustomNotificationHandler", "Constructed Accept Call Intent for PI: action=${acceptCallIntent.action}, cid=${acceptCallIntent.
|
|
80
|
+
Log.d("CustomNotificationHandler", "Constructed Accept Call Intent for PI: action=${acceptCallIntent.action}, cid=${acceptCallIntent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)}, package=${acceptCallIntent.getPackage()}, component=${acceptCallIntent.component?.flattenToString()}, flags=${acceptCallIntent.flags}")
|
|
80
81
|
|
|
81
82
|
// Create PendingIntent for Accept action using getActivity to launch the app
|
|
82
83
|
val requestCodeAccept = callId.cid.hashCode() + 1 // Unique request code for the PendingIntent with offset to avoid collisions
|
|
@@ -150,7 +151,7 @@ class CustomNotificationHandler(
|
|
|
150
151
|
)
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
fun buildNotification(
|
|
154
|
+
private fun buildNotification(
|
|
154
155
|
fullScreenPendingIntent: PendingIntent,
|
|
155
156
|
acceptCallPendingIntent: PendingIntent,
|
|
156
157
|
rejectCallPendingIntent: PendingIntent,
|
|
@@ -31,26 +31,26 @@ class RingtonePlayer(
|
|
|
31
31
|
|
|
32
32
|
fun pauseRinging() {
|
|
33
33
|
Log.d("RingtonePlayer", "Pause ringing")
|
|
34
|
-
try {
|
|
35
|
-
if (!isStopped) {
|
|
36
|
-
mediaPlayer?.pause()
|
|
37
|
-
isPaused = true
|
|
38
|
-
}
|
|
39
|
-
} catch (e: Exception) {
|
|
40
|
-
Log.e("RingtonePlayer", "Error pausing ringtone: ${e.message}")
|
|
41
|
-
}
|
|
34
|
+
// try {
|
|
35
|
+
// if (!isStopped) {
|
|
36
|
+
// mediaPlayer?.pause()
|
|
37
|
+
// isPaused = true
|
|
38
|
+
// }
|
|
39
|
+
// } catch (e: Exception) {
|
|
40
|
+
// Log.e("RingtonePlayer", "Error pausing ringtone: ${e.message}")
|
|
41
|
+
// }
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
fun resumeRinging() {
|
|
45
45
|
Log.d("RingtonePlayer", "Resume ringing")
|
|
46
|
-
try {
|
|
47
|
-
if (!isStopped && isPaused) {
|
|
48
|
-
mediaPlayer?.start()
|
|
49
|
-
isPaused = false
|
|
50
|
-
}
|
|
51
|
-
} catch (e: Exception) {
|
|
52
|
-
Log.e("RingtonePlayer", "Error resuming ringtone: ${e.message}")
|
|
53
|
-
}
|
|
46
|
+
// try {
|
|
47
|
+
// if (!isStopped && isPaused) {
|
|
48
|
+
// mediaPlayer?.start()
|
|
49
|
+
// isPaused = false
|
|
50
|
+
// }
|
|
51
|
+
// } catch (e: Exception) {
|
|
52
|
+
// Log.e("RingtonePlayer", "Error resuming ringtone: ${e.message}")
|
|
53
|
+
// }
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
fun isPaused(): Boolean {
|
|
@@ -59,103 +59,103 @@ class RingtonePlayer(
|
|
|
59
59
|
|
|
60
60
|
fun startRinging() {
|
|
61
61
|
Log.d("RingtonePlayer", "Start ringing")
|
|
62
|
-
try {
|
|
63
|
-
isStopped = false
|
|
64
|
-
isPaused = false
|
|
65
|
-
if (mediaPlayer == null) {
|
|
66
|
-
val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
67
|
-
mediaPlayer = MediaPlayer().apply {
|
|
68
|
-
setDataSource(application, uri)
|
|
69
|
-
isLooping = true
|
|
70
|
-
setAudioAttributes(
|
|
71
|
-
AudioAttributes.Builder()
|
|
72
|
-
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
|
73
|
-
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
74
|
-
.build()
|
|
75
|
-
)
|
|
76
|
-
prepare()
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
val notificationManager = application.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
81
|
-
val notifs = notificationManager.activeNotifications.toList()
|
|
82
|
-
var notificationTime = 0L
|
|
83
|
-
|
|
84
|
-
for (notification in notifs) {
|
|
85
|
-
// First check if it's our notification
|
|
86
|
-
val isOurs = isOurNotification(notification)
|
|
87
|
-
|
|
88
|
-
// Only proceed with ringtone if it's our notification
|
|
89
|
-
if (!isOurs) {
|
|
90
|
-
Log.d("RingtonePlayer", "Skipping notification as it's not our incoming call notification")
|
|
91
|
-
continue
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Cancel our notification
|
|
95
|
-
try {
|
|
96
|
-
Log.d("RingtonePlayer", "Canceling notification/service with id: ${notification.id}")
|
|
97
|
-
this.cancelIncomingCallService()
|
|
98
|
-
} catch (e: Exception) {
|
|
99
|
-
Log.e("RingtonePlayer", "Error cancelling notification: ${e.message}")
|
|
100
|
-
}
|
|
101
|
-
notificationTime = notification.postTime
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (notificationTime > 0) {
|
|
105
|
-
val currentTime = System.currentTimeMillis()
|
|
106
|
-
val elapsedTime = currentTime - notificationTime
|
|
107
|
-
|
|
108
|
-
// Only start playing if we're within the ringtone duration
|
|
109
|
-
if (elapsedTime < DEFAULT_RINGTONE_DURATION) {
|
|
110
|
-
// Get the ringtone duration
|
|
111
|
-
val ringtoneDuration = mediaPlayer?.duration?.toLong() ?: DEFAULT_RINGTONE_DURATION
|
|
112
|
-
|
|
113
|
-
// Calculate the position to seek to
|
|
114
|
-
val seekPosition = (elapsedTime % ringtoneDuration).toInt()
|
|
115
|
-
Log.d("RingtonePlayer", "Seeking to position: $seekPosition ms in ringtone")
|
|
116
|
-
|
|
117
|
-
mediaPlayer?.seekTo(seekPosition)
|
|
118
|
-
mediaPlayer?.start()
|
|
119
|
-
|
|
120
|
-
// Schedule stop at the remaining duration
|
|
121
|
-
val remainingDuration = DEFAULT_RINGTONE_DURATION - elapsedTime
|
|
122
|
-
stopRingtoneRunnable = Runnable { stopRinging() }
|
|
123
|
-
handler.postDelayed(stopRingtoneRunnable!!, remainingDuration)
|
|
124
|
-
|
|
125
|
-
Log.d("RingtonePlayer", "Starting ringtone with offset: $elapsedTime ms, will play for $remainingDuration ms")
|
|
126
|
-
} else {
|
|
127
|
-
Log.d("RingtonePlayer", "Not starting ringtone as elapsed time ($elapsedTime ms) exceeds duration")
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
// If no notification time found, just play normally
|
|
131
|
-
mediaPlayer?.start()
|
|
132
|
-
|
|
133
|
-
// Schedule stop at the default duration
|
|
134
|
-
stopRingtoneRunnable = Runnable { stopRinging() }
|
|
135
|
-
handler.postDelayed(stopRingtoneRunnable!!, DEFAULT_RINGTONE_DURATION)
|
|
136
|
-
|
|
137
|
-
Log.d("RingtonePlayer", "Starting ringtone with default duration")
|
|
138
|
-
}
|
|
139
|
-
} catch (e: Exception) {
|
|
140
|
-
Log.e("RingtonePlayer", "Error playing ringtone: ${e.message}")
|
|
141
|
-
}
|
|
62
|
+
// try {
|
|
63
|
+
// isStopped = false
|
|
64
|
+
// isPaused = false
|
|
65
|
+
// if (mediaPlayer == null) {
|
|
66
|
+
// val uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
67
|
+
// mediaPlayer = MediaPlayer().apply {
|
|
68
|
+
// setDataSource(application, uri)
|
|
69
|
+
// isLooping = true
|
|
70
|
+
// setAudioAttributes(
|
|
71
|
+
// AudioAttributes.Builder()
|
|
72
|
+
// .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
|
73
|
+
// .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
74
|
+
// .build()
|
|
75
|
+
// )
|
|
76
|
+
// prepare()
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
79
|
+
//
|
|
80
|
+
// val notificationManager = application.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
81
|
+
// val notifs = notificationManager.activeNotifications.toList()
|
|
82
|
+
// var notificationTime = 0L
|
|
83
|
+
//
|
|
84
|
+
// for (notification in notifs) {
|
|
85
|
+
// // First check if it's our notification
|
|
86
|
+
// val isOurs = isOurNotification(notification)
|
|
87
|
+
//
|
|
88
|
+
// // Only proceed with ringtone if it's our notification
|
|
89
|
+
// if (!isOurs) {
|
|
90
|
+
// Log.d("RingtonePlayer", "Skipping notification as it's not our incoming call notification")
|
|
91
|
+
// continue
|
|
92
|
+
// }
|
|
93
|
+
//
|
|
94
|
+
// // Cancel our notification
|
|
95
|
+
// try {
|
|
96
|
+
// Log.d("RingtonePlayer", "Canceling notification/service with id: ${notification.id}")
|
|
97
|
+
// this.cancelIncomingCallService()
|
|
98
|
+
// } catch (e: Exception) {
|
|
99
|
+
// Log.e("RingtonePlayer", "Error cancelling notification: ${e.message}")
|
|
100
|
+
// }
|
|
101
|
+
// notificationTime = notification.postTime
|
|
102
|
+
// }
|
|
103
|
+
//
|
|
104
|
+
// if (notificationTime > 0) {
|
|
105
|
+
// val currentTime = System.currentTimeMillis()
|
|
106
|
+
// val elapsedTime = currentTime - notificationTime
|
|
107
|
+
//
|
|
108
|
+
// // Only start playing if we're within the ringtone duration
|
|
109
|
+
// if (elapsedTime < DEFAULT_RINGTONE_DURATION) {
|
|
110
|
+
// // Get the ringtone duration
|
|
111
|
+
// val ringtoneDuration = mediaPlayer?.duration?.toLong() ?: DEFAULT_RINGTONE_DURATION
|
|
112
|
+
//
|
|
113
|
+
// // Calculate the position to seek to
|
|
114
|
+
// val seekPosition = (elapsedTime % ringtoneDuration).toInt()
|
|
115
|
+
// Log.d("RingtonePlayer", "Seeking to position: $seekPosition ms in ringtone")
|
|
116
|
+
//
|
|
117
|
+
// mediaPlayer?.seekTo(seekPosition)
|
|
118
|
+
// mediaPlayer?.start()
|
|
119
|
+
//
|
|
120
|
+
// // Schedule stop at the remaining duration
|
|
121
|
+
// val remainingDuration = DEFAULT_RINGTONE_DURATION - elapsedTime
|
|
122
|
+
// stopRingtoneRunnable = Runnable { stopRinging() }
|
|
123
|
+
// handler.postDelayed(stopRingtoneRunnable!!, remainingDuration)
|
|
124
|
+
//
|
|
125
|
+
// Log.d("RingtonePlayer", "Starting ringtone with offset: $elapsedTime ms, will play for $remainingDuration ms")
|
|
126
|
+
// } else {
|
|
127
|
+
// Log.d("RingtonePlayer", "Not starting ringtone as elapsed time ($elapsedTime ms) exceeds duration")
|
|
128
|
+
// }
|
|
129
|
+
// } else {
|
|
130
|
+
// // If no notification time found, just play normally
|
|
131
|
+
// mediaPlayer?.start()
|
|
132
|
+
//
|
|
133
|
+
// // Schedule stop at the default duration
|
|
134
|
+
// stopRingtoneRunnable = Runnable { stopRinging() }
|
|
135
|
+
// handler.postDelayed(stopRingtoneRunnable!!, DEFAULT_RINGTONE_DURATION)
|
|
136
|
+
//
|
|
137
|
+
// Log.d("RingtonePlayer", "Starting ringtone with default duration")
|
|
138
|
+
// }
|
|
139
|
+
// } catch (e: Exception) {
|
|
140
|
+
// Log.e("RingtonePlayer", "Error playing ringtone: ${e.message}")
|
|
141
|
+
// }
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
fun stopRinging() {
|
|
145
145
|
Log.d("RingtonePlayer", "Stop ringing")
|
|
146
|
-
try {
|
|
147
|
-
isStopped = true
|
|
148
|
-
isPaused = false
|
|
149
|
-
stopRingtoneRunnable?.let { handler.removeCallbacks(it) }
|
|
150
|
-
stopRingtoneRunnable = null
|
|
151
|
-
|
|
152
|
-
mediaPlayer?.stop()
|
|
153
|
-
mediaPlayer?.reset()
|
|
154
|
-
mediaPlayer?.release()
|
|
155
|
-
mediaPlayer = null
|
|
156
|
-
} catch (e: Exception) {
|
|
157
|
-
Log.e("RingtonePlayer", "Error stopping ringtone: ${e.message}")
|
|
158
|
-
}
|
|
146
|
+
// try {
|
|
147
|
+
// isStopped = true
|
|
148
|
+
// isPaused = false
|
|
149
|
+
// stopRingtoneRunnable?.let { handler.removeCallbacks(it) }
|
|
150
|
+
// stopRingtoneRunnable = null
|
|
151
|
+
//
|
|
152
|
+
// mediaPlayer?.stop()
|
|
153
|
+
// mediaPlayer?.reset()
|
|
154
|
+
// mediaPlayer?.release()
|
|
155
|
+
// mediaPlayer = null
|
|
156
|
+
// } catch (e: Exception) {
|
|
157
|
+
// Log.e("RingtonePlayer", "Error stopping ringtone: ${e.message}")
|
|
158
|
+
// }
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
|
|
@@ -1,45 +1,42 @@
|
|
|
1
1
|
package ee.forgr.capacitor.streamcall
|
|
2
2
|
|
|
3
3
|
import TouchInterceptWrapper
|
|
4
|
+
import android.Manifest
|
|
4
5
|
import android.app.Activity
|
|
5
6
|
import android.app.Application
|
|
6
7
|
import android.app.KeyguardManager
|
|
8
|
+
import android.content.BroadcastReceiver
|
|
7
9
|
import android.content.Context
|
|
10
|
+
import android.content.Intent
|
|
11
|
+
import android.content.IntentFilter
|
|
12
|
+
import android.content.pm.PackageManager
|
|
8
13
|
import android.graphics.Color
|
|
9
14
|
import android.media.RingtoneManager
|
|
10
15
|
import android.net.Uri
|
|
16
|
+
import android.os.Build
|
|
11
17
|
import android.os.Bundle
|
|
12
18
|
import android.os.Handler
|
|
13
19
|
import android.os.Looper
|
|
14
20
|
import android.view.View
|
|
15
21
|
import android.view.ViewGroup
|
|
22
|
+
import android.view.WindowManager
|
|
16
23
|
import android.widget.FrameLayout
|
|
24
|
+
import androidx.compose.runtime.collectAsState
|
|
17
25
|
import androidx.compose.ui.platform.ComposeView
|
|
26
|
+
import androidx.core.app.ActivityCompat
|
|
27
|
+
import androidx.core.content.ContextCompat
|
|
18
28
|
import androidx.core.view.isVisible
|
|
19
29
|
import com.getcapacitor.BridgeActivity
|
|
30
|
+
import com.getcapacitor.JSArray
|
|
20
31
|
import com.getcapacitor.JSObject
|
|
21
32
|
import com.getcapacitor.Plugin
|
|
22
33
|
import com.getcapacitor.PluginCall
|
|
23
34
|
import com.getcapacitor.PluginMethod
|
|
24
35
|
import com.getcapacitor.annotation.CapacitorPlugin
|
|
25
|
-
import io.getstream.android.push.permissions.ActivityLifecycleCallbacks
|
|
26
|
-
import io.getstream.video.android.core.Call
|
|
27
|
-
import io.getstream.video.android.core.GEO
|
|
28
|
-
import io.getstream.video.android.core.StreamVideo
|
|
29
|
-
import io.getstream.video.android.core.StreamVideoBuilder
|
|
30
|
-
import io.getstream.video.android.core.notifications.NotificationConfig
|
|
31
|
-
import io.getstream.video.android.core.notifications.NotificationHandler
|
|
32
|
-
import io.getstream.video.android.core.sounds.toSounds
|
|
33
|
-
import io.getstream.video.android.model.StreamCallId
|
|
34
|
-
import io.getstream.video.android.model.User
|
|
35
|
-
import io.getstream.video.android.model.streamCallId
|
|
36
|
-
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
37
|
-
import kotlinx.coroutines.launch
|
|
38
|
-
import io.getstream.video.android.model.Device
|
|
39
|
-
import kotlinx.coroutines.tasks.await
|
|
40
36
|
import com.google.firebase.messaging.FirebaseMessaging
|
|
41
37
|
import io.getstream.android.push.PushProvider
|
|
42
38
|
import io.getstream.android.push.firebase.FirebasePushDeviceGenerator
|
|
39
|
+
import io.getstream.android.push.permissions.ActivityLifecycleCallbacks
|
|
43
40
|
import io.getstream.android.video.generated.models.CallAcceptedEvent
|
|
44
41
|
import io.getstream.android.video.generated.models.CallCreatedEvent
|
|
45
42
|
import io.getstream.android.video.generated.models.CallEndedEvent
|
|
@@ -47,26 +44,36 @@ import io.getstream.android.video.generated.models.CallMissedEvent
|
|
|
47
44
|
import io.getstream.android.video.generated.models.CallRejectedEvent
|
|
48
45
|
import io.getstream.android.video.generated.models.CallRingEvent
|
|
49
46
|
import io.getstream.android.video.generated.models.CallSessionEndedEvent
|
|
47
|
+
import io.getstream.android.video.generated.models.CallSessionParticipantLeftEvent
|
|
50
48
|
import io.getstream.android.video.generated.models.CallSessionStartedEvent
|
|
51
|
-
import io.getstream.video.android.core.sounds.RingingConfig
|
|
52
|
-
import kotlinx.coroutines.CoroutineScope
|
|
53
|
-
import kotlinx.coroutines.Dispatchers
|
|
54
|
-
import android.Manifest
|
|
55
|
-
import android.content.pm.PackageManager
|
|
56
|
-
import androidx.core.app.ActivityCompat
|
|
57
|
-
import androidx.core.content.ContextCompat
|
|
58
49
|
import io.getstream.android.video.generated.models.VideoEvent
|
|
50
|
+
import io.getstream.log.Priority
|
|
59
51
|
import io.getstream.video.android.compose.theme.VideoTheme
|
|
60
52
|
import io.getstream.video.android.compose.ui.components.call.activecall.CallContent
|
|
61
|
-
import
|
|
53
|
+
import io.getstream.video.android.core.Call
|
|
62
54
|
import io.getstream.video.android.core.CameraDirection
|
|
63
|
-
import android.
|
|
64
|
-
import android.content.Intent
|
|
65
|
-
import android.content.IntentFilter
|
|
66
|
-
import com.getcapacitor.JSArray
|
|
67
|
-
import io.getstream.android.video.generated.models.CallSessionParticipantLeftEvent
|
|
55
|
+
import io.getstream.video.android.core.GEO
|
|
68
56
|
import io.getstream.video.android.core.RealtimeConnection
|
|
57
|
+
import io.getstream.video.android.core.StreamVideo
|
|
58
|
+
import io.getstream.video.android.core.StreamVideoBuilder
|
|
59
|
+
import io.getstream.video.android.core.call.CallType
|
|
69
60
|
import io.getstream.video.android.core.events.ParticipantLeftEvent
|
|
61
|
+
import io.getstream.video.android.core.logging.LoggingLevel
|
|
62
|
+
import io.getstream.video.android.core.notifications.NotificationConfig
|
|
63
|
+
import io.getstream.video.android.core.notifications.NotificationHandler
|
|
64
|
+
import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry
|
|
65
|
+
import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations
|
|
66
|
+
import io.getstream.video.android.core.sounds.RingingConfig
|
|
67
|
+
import io.getstream.video.android.core.sounds.toSounds
|
|
68
|
+
import io.getstream.video.android.model.Device
|
|
69
|
+
import io.getstream.video.android.model.StreamCallId
|
|
70
|
+
import io.getstream.video.android.model.User
|
|
71
|
+
import io.getstream.video.android.model.streamCallId
|
|
72
|
+
import kotlinx.coroutines.CoroutineScope
|
|
73
|
+
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
74
|
+
import kotlinx.coroutines.Dispatchers
|
|
75
|
+
import kotlinx.coroutines.launch
|
|
76
|
+
import kotlinx.coroutines.tasks.await
|
|
70
77
|
|
|
71
78
|
// I am not a religious pearson, but at this point, I am not sure even god himself would understand this code
|
|
72
79
|
// It's a spaghetti-like, tangled, unreadable mess and frankly, I am deeply sorry for the code crimes commited in the Android impl
|
|
@@ -76,7 +83,6 @@ public class StreamCallPlugin : Plugin() {
|
|
|
76
83
|
private var state: State = State.NOT_INITIALIZED
|
|
77
84
|
private var overlayView: ComposeView? = null
|
|
78
85
|
private var barrierView: View? = null
|
|
79
|
-
private var ringtonePlayer: RingtonePlayer? = null
|
|
80
86
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
81
87
|
private var savedContext: Context? = null
|
|
82
88
|
private var bootedToHandleCall: Boolean = false
|
|
@@ -112,62 +118,15 @@ public class StreamCallPlugin : Plugin() {
|
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
override fun handleOnPause() {
|
|
115
|
-
this.ringtonePlayer.let { it?.pauseRinging() }
|
|
116
121
|
super.handleOnPause()
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
override fun handleOnResume() {
|
|
120
|
-
this.ringtonePlayer.let { it?.resumeRinging() }
|
|
121
125
|
super.handleOnResume()
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
override fun load() {
|
|
125
129
|
// general init
|
|
126
|
-
ringtonePlayer = RingtonePlayer(
|
|
127
|
-
this.activity.application,
|
|
128
|
-
cancelIncomingCallService = {
|
|
129
|
-
val streamVideoClient = this.streamVideoClient
|
|
130
|
-
if (streamVideoClient == null) {
|
|
131
|
-
android.util.Log.d("StreamCallPlugin", "StreamVideo SDK client is null, no incoming call notification can be constructed")
|
|
132
|
-
return@RingtonePlayer
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
val callServiceClass = Class.forName("io.getstream.video.android.core.notifications.internal.service.CallService")
|
|
137
|
-
val companionClass = callServiceClass.declaredClasses.first { it.simpleName == "Companion" }
|
|
138
|
-
// Instead of getting INSTANCE, we'll get the companion object through the enclosing class
|
|
139
|
-
val companionField = callServiceClass.getDeclaredField("Companion")
|
|
140
|
-
companionField.isAccessible = true
|
|
141
|
-
val companionInstance = companionField.get(null)
|
|
142
|
-
|
|
143
|
-
val removeIncomingCallMethod = companionClass.getDeclaredMethod(
|
|
144
|
-
"removeIncomingCall",
|
|
145
|
-
Context::class.java,
|
|
146
|
-
Class.forName("io.getstream.video.android.model.StreamCallId"),
|
|
147
|
-
Class.forName("io.getstream.video.android.core.notifications.internal.service.CallServiceConfig")
|
|
148
|
-
)
|
|
149
|
-
removeIncomingCallMethod.isAccessible = true
|
|
150
|
-
|
|
151
|
-
// Get the default config using reflection
|
|
152
|
-
val defaultConfigClass = Class.forName("io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations")
|
|
153
|
-
val defaultField = defaultConfigClass.getDeclaredField("INSTANCE")
|
|
154
|
-
val defaultInstance = defaultField.get(null)
|
|
155
|
-
val defaultMethod = defaultConfigClass.getDeclaredMethod("getDefault")
|
|
156
|
-
val defaultConfig = defaultMethod.invoke(defaultInstance)
|
|
157
|
-
|
|
158
|
-
val app = this.activity.application
|
|
159
|
-
val cId = streamVideoClient.state.ringingCall.value?.cid?.let { StreamCallId.fromCallCid(it) }
|
|
160
|
-
if (app == null || cId == null || defaultConfig == null) {
|
|
161
|
-
android.util.Log.e("StreamCallPlugin", "Some required parameters are null - app: ${app == null}, cId: ${cId == null}, defaultConfig: ${defaultConfig == null}")
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Call the method
|
|
165
|
-
removeIncomingCallMethod.invoke(companionInstance, app, cId, defaultConfig)
|
|
166
|
-
} catch (e : Throwable) {
|
|
167
|
-
android.util.Log.e("StreamCallPlugin", "Reflecting streamNotificationManager and the config DID NOT work", e);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
)
|
|
171
130
|
initializeStreamVideo()
|
|
172
131
|
setupViews()
|
|
173
132
|
super.load()
|
|
@@ -206,6 +165,8 @@ public class StreamCallPlugin : Plugin() {
|
|
|
206
165
|
|
|
207
166
|
if (action === "io.getstream.video.android.action.INCOMING_CALL") {
|
|
208
167
|
android.util.Log.d("StreamCallPlugin", "handleOnNewIntent: Matched INCOMING_CALL action")
|
|
168
|
+
// We need to make sure the activity is visible on locked screen in such case
|
|
169
|
+
changeActivityAsVisibleOnLockScreen(this@StreamCallPlugin.activity, true)
|
|
209
170
|
activity?.runOnUiThread {
|
|
210
171
|
val cid = intent.streamCallId(NotificationHandler.INTENT_EXTRA_CALL_CID)
|
|
211
172
|
android.util.Log.d("StreamCallPlugin", "handleOnNewIntent: INCOMING_CALL - Extracted cid: $cid")
|
|
@@ -213,8 +174,6 @@ public class StreamCallPlugin : Plugin() {
|
|
|
213
174
|
android.util.Log.d("StreamCallPlugin", "handleOnNewIntent: INCOMING_CALL - cid is not null, processing.")
|
|
214
175
|
val call = streamVideoClient?.call(id = cid.id, type = cid.type)
|
|
215
176
|
android.util.Log.d("StreamCallPlugin", "handleOnNewIntent: INCOMING_CALL - Got call object: ${call?.id}")
|
|
216
|
-
// Start ringtone only; UI handled in web layer
|
|
217
|
-
ringtonePlayer?.startRinging()
|
|
218
177
|
|
|
219
178
|
// Try to get caller information from the call
|
|
220
179
|
kotlinx.coroutines.GlobalScope.launch {
|
|
@@ -296,9 +255,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
296
255
|
kotlinx.coroutines.GlobalScope.launch {
|
|
297
256
|
try {
|
|
298
257
|
call.reject()
|
|
299
|
-
|
|
300
|
-
// Stop ringtone
|
|
301
|
-
ringtonePlayer?.stopRinging()
|
|
258
|
+
changeActivityAsVisibleOnLockScreen(this@StreamCallPlugin.activity, false)
|
|
302
259
|
|
|
303
260
|
// Notify that call has ended using our helper
|
|
304
261
|
updateCallStatusAndNotify(call.id, "rejected")
|
|
@@ -547,6 +504,7 @@ public class StreamCallPlugin : Plugin() {
|
|
|
547
504
|
)
|
|
548
505
|
|
|
549
506
|
val soundsConfig = incomingOnlyRingingConfig()
|
|
507
|
+
|
|
550
508
|
// Initialize StreamVideo client
|
|
551
509
|
streamVideoClient = StreamVideoBuilder(
|
|
552
510
|
context = contextToUse,
|
|
@@ -555,8 +513,8 @@ public class StreamCallPlugin : Plugin() {
|
|
|
555
513
|
user = savedCredentials.user,
|
|
556
514
|
token = savedCredentials.tokenValue,
|
|
557
515
|
notificationConfig = notificationConfig,
|
|
558
|
-
sounds = soundsConfig.toSounds()
|
|
559
|
-
|
|
516
|
+
sounds = soundsConfig.toSounds(),
|
|
517
|
+
// loggingLevel = LoggingLevel(priority = Priority.DEBUG)
|
|
560
518
|
).build()
|
|
561
519
|
|
|
562
520
|
// don't do event handler registration when activity may be null
|
|
@@ -758,7 +716,6 @@ public class StreamCallPlugin : Plugin() {
|
|
|
758
716
|
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
|
759
717
|
if (keyguardManager.isKeyguardLocked) {
|
|
760
718
|
android.util.Log.d("StreamCallPlugin", "Stop ringing and move to background")
|
|
761
|
-
this.ringtonePlayer?.stopRinging()
|
|
762
719
|
moveAllActivitiesToBackgroundOrKill(context)
|
|
763
720
|
}
|
|
764
721
|
|
|
@@ -865,9 +822,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
865
822
|
// Notify that a call has started or state updated (e.g., participants changed but still active)
|
|
866
823
|
// The actual check for "last participant" is now handled by CallSessionParticipantLeftEvent
|
|
867
824
|
updateCallStatusAndNotify(call.cid, "joined")
|
|
825
|
+
// Make sure activity is visible on lock screen
|
|
826
|
+
changeActivityAsVisibleOnLockScreen(this@StreamCallPlugin.activity, true)
|
|
868
827
|
} ?: run {
|
|
869
828
|
// Notify that call has ended using our helper
|
|
870
829
|
updateCallStatusAndNotify("", "left")
|
|
830
|
+
changeActivityAsVisibleOnLockScreen(this@StreamCallPlugin.activity, false)
|
|
871
831
|
}
|
|
872
832
|
}
|
|
873
833
|
}
|
|
@@ -967,9 +927,6 @@ public class StreamCallPlugin : Plugin() {
|
|
|
967
927
|
kotlinx.coroutines.GlobalScope.launch {
|
|
968
928
|
try {
|
|
969
929
|
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Coroutine started for call ${call.id}")
|
|
970
|
-
// Stop ringtone
|
|
971
|
-
ringtonePlayer?.stopRinging()
|
|
972
|
-
android.util.Log.d("StreamCallPlugin", "internalAcceptCall: Ringtone player stopped for call ${call.id}")
|
|
973
930
|
|
|
974
931
|
// Hide incoming call view first
|
|
975
932
|
runOnMainThread {
|
|
@@ -1274,6 +1231,12 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1274
1231
|
android.util.Log.d("StreamCallPlugin", "Leaving call $callId (not creator, >2 participants)")
|
|
1275
1232
|
call.leave()
|
|
1276
1233
|
}
|
|
1234
|
+
|
|
1235
|
+
// Here, we'll also mark the activity as not-visible on lock screen
|
|
1236
|
+
this@StreamCallPlugin.savedActivity?.let {
|
|
1237
|
+
changeActivityAsVisibleOnLockScreen(it, false)
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1277
1240
|
} catch (e: Exception) {
|
|
1278
1241
|
android.util.Log.e("StreamCallPlugin", "Error getting call info for $callId, defaulting to leave()", e)
|
|
1279
1242
|
// Fallback to leave if we can't determine the call info
|
|
@@ -1328,7 +1291,6 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1328
1291
|
}
|
|
1329
1292
|
overlayView?.isVisible = false
|
|
1330
1293
|
bridge?.webView?.setBackgroundColor(Color.WHITE) // Restore webview opacity
|
|
1331
|
-
this@StreamCallPlugin.ringtonePlayer?.stopRinging()
|
|
1332
1294
|
|
|
1333
1295
|
// Also hide incoming call view if visible
|
|
1334
1296
|
android.util.Log.d("StreamCallPlugin", "Hiding incoming call view for call $callId")
|
|
@@ -1339,6 +1301,29 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1339
1301
|
updateCallStatusAndNotify(callId, "left")
|
|
1340
1302
|
}
|
|
1341
1303
|
|
|
1304
|
+
private fun changeActivityAsVisibleOnLockScreen(activity: Activity, visible: Boolean) {
|
|
1305
|
+
if (visible) {
|
|
1306
|
+
// Ensure the activity is visible over the lock screen when launched via full-screen intent
|
|
1307
|
+
android.util.Log.d("StreamCallPlugin", "Mark the mainActivity as visible on the lockscreen")
|
|
1308
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
1309
|
+
activity.setShowWhenLocked(true)
|
|
1310
|
+
activity.setTurnScreenOn(true)
|
|
1311
|
+
} else {
|
|
1312
|
+
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
1313
|
+
}
|
|
1314
|
+
} else {
|
|
1315
|
+
// Ensure the activity is NOT visible over the lock screen when launched via full-screen intent
|
|
1316
|
+
android.util.Log.d("StreamCallPlugin", "Clear the flag for the mainActivity for visible on the lockscreen")
|
|
1317
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
|
1318
|
+
activity.setShowWhenLocked(false)
|
|
1319
|
+
activity.setTurnScreenOn(false)
|
|
1320
|
+
} else {
|
|
1321
|
+
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1342
1327
|
@OptIn(DelicateCoroutinesApi::class)
|
|
1343
1328
|
private fun transEndCallRaw(call: Call) {
|
|
1344
1329
|
val callId = call.id
|
|
@@ -1595,8 +1580,8 @@ public class StreamCallPlugin : Plugin() {
|
|
|
1595
1580
|
runOnMainThread {
|
|
1596
1581
|
android.util.Log.d("StreamCallPlugin", "Hiding UI elements for call $callCid (one-time cleanup)")
|
|
1597
1582
|
overlayView?.isVisible = false
|
|
1598
|
-
|
|
1599
|
-
|
|
1583
|
+
// here we will also make sure we don't show on lock screen
|
|
1584
|
+
changeActivityAsVisibleOnLockScreen(this.activity, false)
|
|
1600
1585
|
}
|
|
1601
1586
|
|
|
1602
1587
|
android.util.Log.d("StreamCallPlugin", "Cleaned up resources for ended call: $callCid")
|
package/dist/esm/web.js
CHANGED
|
@@ -21,18 +21,20 @@ export class StreamCallWeb extends WebPlugin {
|
|
|
21
21
|
role: event.call.created_by.role,
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
if (!this.currentCall) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
// if (!this.currentCall) {
|
|
25
|
+
console.log('Creating new call', event.call.id);
|
|
26
|
+
this.currentCall = (_b = this.client) === null || _b === void 0 ? void 0 : _b.call(event.call.type, event.call.id);
|
|
27
|
+
// this.currentActiveCallId = this.currentCall?.cid;
|
|
28
|
+
setTimeout(() => {
|
|
28
29
|
this.notifyListeners('callEvent', {
|
|
29
30
|
callId: event.call.id,
|
|
30
31
|
state: CallingState.RINGING,
|
|
31
32
|
caller,
|
|
32
33
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
}, 100);
|
|
35
|
+
// Clear previous responses when a new call starts
|
|
36
|
+
this.participantResponses.clear();
|
|
37
|
+
// }
|
|
36
38
|
if (this.currentCall) {
|
|
37
39
|
console.log('Call found', this.currentCall.id);
|
|
38
40
|
this.callStateSubscription = (_c = this.currentCall) === null || _c === void 0 ? void 0 : _c.state.callingState$.subscribe((s) => {
|