-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathVoipCallService.kt
More file actions
163 lines (146 loc) · 5.55 KB
/
VoipCallService.kt
File metadata and controls
163 lines (146 loc) · 5.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package chat.rocket.reactnative.voip
import android.app.Notification
import android.app.NotificationChannel
import chat.rocket.reactnative.BuildConfig
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import chat.rocket.reactnative.MainActivity
/**
* Foreground service that keeps the VoIP call alive when the app moves to the background.
* Required because Android terminates background processes without a foreground service,
* which would drop the active audio session.
*
* Started on call accept, stopped on hangup.
*/
class VoipCallService : Service() {
companion object {
private const val TAG = "RocketChat.VoipCallService"
private const val CHANNEL_ID = "voip-call-service"
private const val CHANNEL_NAME = "VoIP Call"
private const val NOTIFICATION_ID = 1
private const val ACTION_START = "chat.rocket.reactnative.voip.START_SERVICE"
private const val ACTION_STOP = "chat.rocket.reactnative.voip.STOP_SERVICE"
const val EXTRA_CALL_ID = "callId"
private var isRunning = false
@JvmStatic
fun startService(context: android.content.Context, callId: String) {
val intent = Intent(context, VoipCallService::class.java).apply {
action = ACTION_START
putExtra(EXTRA_CALL_ID, callId)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
@JvmStatic
fun stopService(context: android.content.Context) {
val intent = Intent(context, VoipCallService::class.java).apply {
action = ACTION_STOP
}
context.stopService(intent)
}
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
Log.d(TAG, "VoipCallService created")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_STOP -> {
isRunning = false
Log.d(TAG, "Stopping VoipCallService")
stopSelf(startId)
return START_NOT_STICKY
}
ACTION_START -> {
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "unknown"
if (BuildConfig.DEBUG) {
Log.d(TAG, "Starting VoipCallService for callId: $callId")
}
if (!isRunning) {
isRunning = true
startForegroundWithNotification(callId)
} else {
Log.d(TAG, "Service already running, skipping duplicate start")
}
return START_NOT_STICKY
}
else -> {
Log.w(TAG, "Unknown action: ${intent?.action}")
stopSelf(startId)
return START_NOT_STICKY
}
}
}
private fun startForegroundWithNotification(callId: String) {
val notification = buildNotification(callId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
)
} else {
startForeground(NOTIFICATION_ID, notification)
}
if (BuildConfig.DEBUG) {
Log.d(TAG, "Started foreground with notification for callId: $callId")
}
}
private fun buildNotification(callId: String): Notification {
// Pending intent: tapping the notification opens the app.
val pendingIntent = PendingIntent.getActivity(
this,
0,
Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
},
PendingIntent.FLAG_UPDATE_CURRENT or if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
}
)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("VoIP Call")
.setContentText("Call in progress")
.setSmallIcon(getApplicationInfo().icon)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.build()
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW
).apply {
description = "VoIP call in progress"
setShowBadge(false)
}
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager?.createNotificationChannel(channel)
}
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
isRunning = false
Log.d(TAG, "VoipCallService destroyed")
super.onDestroy()
}
}