Compare commits
2 commits
abe086b469
...
0940fcb908
| Author | SHA1 | Date | |
|---|---|---|---|
| 0940fcb908 | |||
| 4285891d8d |
4 changed files with 125 additions and 3 deletions
|
|
@ -9,10 +9,23 @@
|
|||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<!-- Camera features -->
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
|
||||
<!-- Microphone feature -->
|
||||
<uses-feature android:name="android.hardware.microphone" android:required="true" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name=".LinphoneVoipService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="phoneCall"
|
||||
android:stopWithTask="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler
|
|||
val callTo = call.argument<String>("callTo")!!
|
||||
val isVideoEnabled = call.argument<Boolean>("isVideoEnabled")!!
|
||||
linphoneBridge.makeCall(callTo, isVideoEnabled)
|
||||
startCallService()
|
||||
result.success(true)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "makeCall: ${e.message}")
|
||||
|
|
@ -147,11 +148,13 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler
|
|||
|
||||
"answerCall" -> {
|
||||
val res = linphoneBridge.answerCall()
|
||||
if (res) startCallService()
|
||||
result.success(res)
|
||||
}
|
||||
|
||||
"hangupCall" -> {
|
||||
val res = linphoneBridge.hangupCall()
|
||||
if (res) startCallService()
|
||||
result.success(res)
|
||||
}
|
||||
|
||||
|
|
@ -240,5 +243,27 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler
|
|||
|
||||
companion object {
|
||||
const val TAG = "LiblinphoneFlutterPlugin"
|
||||
|
||||
private fun startCallService() {
|
||||
try {
|
||||
val intent = Intent(activity.applicationContext, LinphoneVoipService::class.java)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
activity.startForegroundService(intent)
|
||||
} else {
|
||||
activity.startService(intent)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to start call service: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopCallService() {
|
||||
try {
|
||||
val intent = Intent(activity.applicationContext, LinphoneVoipService::class.java)
|
||||
activity.stopService(intent)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to stop call service: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
package xyz.nuark.liblinphone_flutter
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import android.content.pm.ServiceInfo
|
||||
|
||||
class LinphoneVoipService : Service() {
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
private val channelId = "linphone_voip_call_channel"
|
||||
private val TAG = "LinphoneVoipService"
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
createNotificationChannel()
|
||||
acquireWakeLock()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val notification = createNotification()
|
||||
|
||||
// Android 14+ requires explicit service type in startForeground
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(100, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL)
|
||||
} else {
|
||||
startForeground(100, notification)
|
||||
}
|
||||
|
||||
Log.i(TAG, "Foreground service started. Mic will stay active.")
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
releaseWakeLock()
|
||||
Log.i(TAG, "Foreground service stopped.")
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
channelId,
|
||||
"Active VoIP Call",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setShowBadge(false)
|
||||
setSound(null, null)
|
||||
}
|
||||
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNotification(): Notification {
|
||||
return NotificationCompat.Builder(this, channelId)
|
||||
.setContentTitle("Voice Call")
|
||||
.setContentText("Call in progress")
|
||||
.setSmallIcon(android.R.drawable.ic_menu_call) // Replace with your app's call icon
|
||||
.setOngoing(true)
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun acquireWakeLock() {
|
||||
val pm = getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Linphone::CallWakeLock")
|
||||
wakeLock?.acquire(30 * 60 * 1000L) // 30 min fallback timeout
|
||||
}
|
||||
|
||||
private fun releaseWakeLock() {
|
||||
wakeLock?.takeIf { it.isHeld }?.release()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue