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.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<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 -->
|
<!-- Camera features -->
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
|
|
||||||
<!-- Microphone feature -->
|
<!-- Microphone feature -->
|
||||||
<uses-feature android:name="android.hardware.microphone" android:required="true" />
|
<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>
|
</manifest>
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler
|
||||||
val callTo = call.argument<String>("callTo")!!
|
val callTo = call.argument<String>("callTo")!!
|
||||||
val isVideoEnabled = call.argument<Boolean>("isVideoEnabled")!!
|
val isVideoEnabled = call.argument<Boolean>("isVideoEnabled")!!
|
||||||
linphoneBridge.makeCall(callTo, isVideoEnabled)
|
linphoneBridge.makeCall(callTo, isVideoEnabled)
|
||||||
|
startCallService()
|
||||||
result.success(true)
|
result.success(true)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "makeCall: ${e.message}")
|
Log.e(TAG, "makeCall: ${e.message}")
|
||||||
|
|
@ -147,11 +148,13 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler
|
||||||
|
|
||||||
"answerCall" -> {
|
"answerCall" -> {
|
||||||
val res = linphoneBridge.answerCall()
|
val res = linphoneBridge.answerCall()
|
||||||
|
if (res) startCallService()
|
||||||
result.success(res)
|
result.success(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
"hangupCall" -> {
|
"hangupCall" -> {
|
||||||
val res = linphoneBridge.hangupCall()
|
val res = linphoneBridge.hangupCall()
|
||||||
|
if (res) startCallService()
|
||||||
result.success(res)
|
result.success(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,5 +243,27 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "LiblinphoneFlutterPlugin"
|
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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -201,9 +201,9 @@ class LinphoneBridge(
|
||||||
core = factory.createCore(null, null, activity.baseContext)
|
core = factory.createCore(null, null, activity.baseContext)
|
||||||
core.addListener(coreListener)
|
core.addListener(coreListener)
|
||||||
|
|
||||||
core.isIpv6Enabled = false
|
core.isIpv6Enabled = false
|
||||||
core.config.setInt("net", "ipv6", 0)
|
core.config.setInt("net", "ipv6", 0)
|
||||||
core.config.sync()
|
core.config.sync()
|
||||||
|
|
||||||
// Enable video
|
// Enable video
|
||||||
core.isVideoCaptureEnabled = true
|
core.isVideoCaptureEnabled = true
|
||||||
|
|
|
||||||
|
|
@ -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