forked from eatvio/eatvio_app
feat(video_calls): fixed call ended push handling
This commit is contained in:
@@ -25,8 +25,10 @@ class MainActivity: FlutterActivity() {
|
|||||||
|
|
||||||
private val channelName = "com.eatvioapp/hms_push"
|
private val channelName = "com.eatvioapp/hms_push"
|
||||||
private val videoCallPipChannelName = "com.eatvioapp/video_call_pip"
|
private val videoCallPipChannelName = "com.eatvioapp/video_call_pip"
|
||||||
|
private val videoCallPushChannelName = "com.eatvioapp/video_call_push"
|
||||||
private var methodChannel: MethodChannel? = null
|
private var methodChannel: MethodChannel? = null
|
||||||
private var videoCallPipChannel: MethodChannel? = null
|
private var videoCallPipChannel: MethodChannel? = null
|
||||||
|
private var videoCallPushChannel: MethodChannel? = null
|
||||||
private var videoCallPipEnabled: Boolean = false
|
private var videoCallPipEnabled: Boolean = false
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@@ -44,6 +46,20 @@ class MainActivity: FlutterActivity() {
|
|||||||
|
|
||||||
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
|
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
|
||||||
videoCallPipChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, videoCallPipChannelName)
|
videoCallPipChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, videoCallPipChannelName)
|
||||||
|
videoCallPushChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, videoCallPushChannelName)
|
||||||
|
videoCallPushChannel?.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"dismissIncomingCalls" -> {
|
||||||
|
try {
|
||||||
|
VideoCallPushBridge.dismissAllIncomingCalls(applicationContext)
|
||||||
|
result.success(null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
result.error("DISMISS_INCOMING_CALLS_FAILED", e.message, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
videoCallPipChannel?.setMethodCallHandler { call, result ->
|
videoCallPipChannel?.setMethodCallHandler { call, result ->
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"setPipEnabled" -> {
|
"setPipEnabled" -> {
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ package com.eatvioapp.app
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.hiennv.flutter_callkit_incoming.CallkitConstants
|
import com.hiennv.flutter_callkit_incoming.CallkitConstants
|
||||||
import com.hiennv.flutter_callkit_incoming.CallkitIncomingBroadcastReceiver
|
|
||||||
import com.hiennv.flutter_callkit_incoming.CallkitNotificationManager
|
import com.hiennv.flutter_callkit_incoming.CallkitNotificationManager
|
||||||
|
import com.hiennv.flutter_callkit_incoming.CallkitNotificationService
|
||||||
import com.hiennv.flutter_callkit_incoming.CallkitSoundPlayerManager
|
import com.hiennv.flutter_callkit_incoming.CallkitSoundPlayerManager
|
||||||
import com.hiennv.flutter_callkit_incoming.Data
|
import com.hiennv.flutter_callkit_incoming.Data
|
||||||
|
import com.hiennv.flutter_callkit_incoming.FlutterCallkitIncomingPlugin
|
||||||
import com.hiennv.flutter_callkit_incoming.addCall
|
import com.hiennv.flutter_callkit_incoming.addCall
|
||||||
import com.hiennv.flutter_callkit_incoming.getDataActiveCalls
|
import com.hiennv.flutter_callkit_incoming.getDataActiveCalls
|
||||||
import com.hiennv.flutter_callkit_incoming.removeAllCalls
|
import com.hiennv.flutter_callkit_incoming.removeAllCalls
|
||||||
@@ -16,6 +21,10 @@ import java.util.HashMap
|
|||||||
object VideoCallPushBridge {
|
object VideoCallPushBridge {
|
||||||
private const val TAG = "VideoCallPushBridge"
|
private const val TAG = "VideoCallPushBridge"
|
||||||
private const val ANDROID_ACCENT_COLOR = "#2787F5"
|
private const val ANDROID_ACCENT_COLOR = "#2787F5"
|
||||||
|
private const val MISSED_CALL_CLEANUP_DELAY_MS = 250L
|
||||||
|
|
||||||
|
private var activeSoundManager: CallkitSoundPlayerManager? = null
|
||||||
|
private var activeNotificationManager: CallkitNotificationManager? = null
|
||||||
|
|
||||||
fun isIncomingVideoCall(data: Map<String, String>): Boolean {
|
fun isIncomingVideoCall(data: Map<String, String>): Boolean {
|
||||||
val rawType = data["type"]
|
val rawType = data["type"]
|
||||||
@@ -90,6 +99,8 @@ object VideoCallPushBridge {
|
|||||||
|
|
||||||
val soundManager = CallkitSoundPlayerManager(appContext)
|
val soundManager = CallkitSoundPlayerManager(appContext)
|
||||||
val notificationManager = CallkitNotificationManager(appContext, soundManager)
|
val notificationManager = CallkitNotificationManager(appContext, soundManager)
|
||||||
|
activeSoundManager = soundManager
|
||||||
|
activeNotificationManager = notificationManager
|
||||||
notificationManager.createNotificationChanel(callData)
|
notificationManager.createNotificationChanel(callData)
|
||||||
notificationManager.showIncomingNotification(callData)
|
notificationManager.showIncomingNotification(callData)
|
||||||
|
|
||||||
@@ -105,28 +116,108 @@ object VideoCallPushBridge {
|
|||||||
val calls = getDataActiveCalls(appContext)
|
val calls = getDataActiveCalls(appContext)
|
||||||
Log.i(TAG, "dismissAllIncomingCalls: active calls=${calls.size}")
|
Log.i(TAG, "dismissAllIncomingCalls: active calls=${calls.size}")
|
||||||
|
|
||||||
val soundManager = CallkitSoundPlayerManager(appContext)
|
stopIncomingRingtone(appContext)
|
||||||
val notificationManager = CallkitNotificationManager(appContext, soundManager)
|
|
||||||
|
|
||||||
for (call in calls) {
|
val notificationManager = resolveNotificationManager(appContext)
|
||||||
val bundle = call.toBundle()
|
val bundles = calls.map { it.toBundle() }
|
||||||
|
|
||||||
|
for (bundle in bundles) {
|
||||||
try {
|
try {
|
||||||
notificationManager.clearIncomingNotification(bundle, isAccepted = false)
|
dismissIncomingCallNotification(appContext, notificationManager, bundle)
|
||||||
appContext.sendBroadcast(
|
|
||||||
CallkitIncomingBroadcastReceiver.getIntentEnded(appContext, bundle),
|
|
||||||
)
|
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
Log.w(TAG, "dismissAllIncomingCalls: failed for call ${call.id}", error)
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"dismissAllIncomingCalls: failed for call ${bundle.getString(CallkitConstants.EXTRA_CALLKIT_ID)}",
|
||||||
|
error,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
CallkitNotificationService.stopService(appContext)
|
||||||
|
} catch (error: Exception) {
|
||||||
|
Log.w(TAG, "dismissAllIncomingCalls: failed to stop callkit service", error)
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
removeAllCalls(appContext)
|
removeAllCalls(appContext)
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
Log.w(TAG, "dismissAllIncomingCalls: failed to remove persisted calls", error)
|
Log.w(TAG, "dismissAllIncomingCalls: failed to remove persisted calls", error)
|
||||||
}
|
}
|
||||||
|
|
||||||
soundManager.stop()
|
stopIncomingRingtone(appContext)
|
||||||
|
scheduleMissedCallCleanup(notificationManager, bundles)
|
||||||
|
activeSoundManager = null
|
||||||
|
activeNotificationManager = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveNotificationManager(context: Context): CallkitNotificationManager {
|
||||||
|
activeNotificationManager?.let { return it }
|
||||||
|
FlutterCallkitIncomingPlugin.getInstance()
|
||||||
|
?.getCallkitNotificationManager()
|
||||||
|
?.let { return it }
|
||||||
|
|
||||||
|
val soundManager = CallkitSoundPlayerManager(context)
|
||||||
|
return CallkitNotificationManager(context, soundManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopIncomingRingtone(context: Context) {
|
||||||
|
activeSoundManager?.stop()
|
||||||
|
FlutterCallkitIncomingPlugin.getInstance()
|
||||||
|
?.getCallkitSoundPlayerManager()
|
||||||
|
?.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the incoming notification before cancel so Android does not fire the
|
||||||
|
* timeout delete intent (which would show a missed-call notification).
|
||||||
|
*/
|
||||||
|
private fun dismissIncomingCallNotification(
|
||||||
|
context: Context,
|
||||||
|
notificationManager: CallkitNotificationManager,
|
||||||
|
bundle: Bundle,
|
||||||
|
) {
|
||||||
|
val notificationId = incomingNotificationId(bundle)
|
||||||
|
|
||||||
|
val silentNotification = NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
CallkitNotificationManager.NOTIFICATION_CHANNEL_ID_INCOMING,
|
||||||
|
)
|
||||||
|
.setSmallIcon(context.applicationInfo.icon)
|
||||||
|
.setContentTitle("")
|
||||||
|
.setSilent(true)
|
||||||
|
.setOngoing(false)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val notificationManagerCompat = NotificationManagerCompat.from(context)
|
||||||
|
notificationManagerCompat.notify(notificationId, silentNotification)
|
||||||
|
notificationManagerCompat.cancel(notificationId)
|
||||||
|
|
||||||
|
bundle.putBoolean(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_SHOW, false)
|
||||||
|
notificationManager.clearIncomingNotification(bundle, isAccepted = true)
|
||||||
|
notificationManager.clearMissCallNotification(bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleMissedCallCleanup(
|
||||||
|
notificationManager: CallkitNotificationManager,
|
||||||
|
bundles: List<Bundle>,
|
||||||
|
) {
|
||||||
|
if (bundles.isEmpty()) return
|
||||||
|
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
for (bundle in bundles) {
|
||||||
|
try {
|
||||||
|
notificationManager.clearMissCallNotification(bundle)
|
||||||
|
} catch (error: Exception) {
|
||||||
|
Log.w(TAG, "dismissAllIncomingCalls: delayed missed-call cleanup failed", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, MISSED_CALL_CLEANUP_DELAY_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun incomingNotificationId(bundle: Bundle): Int {
|
||||||
|
return bundle.getString(CallkitConstants.EXTRA_CALLKIT_ID, "callkit_incoming").hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildCallDataBundle(data: Map<String, String>): Bundle? {
|
private fun buildCallDataBundle(data: Map<String, String>): Bundle? {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,8 +1,26 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
|
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
|
||||||
|
|
||||||
/// Dismisses incoming call UI after a remote `video_call_ended` push.
|
/// Dismisses incoming call UI after a remote `video_call_ended` push.
|
||||||
abstract final class VideoCallEndedPushHandler {
|
abstract final class VideoCallEndedPushHandler {
|
||||||
|
static const MethodChannel _androidPushChannel = MethodChannel(
|
||||||
|
'com.eatvioapp/video_call_push',
|
||||||
|
);
|
||||||
|
|
||||||
static Future<void> dismissIncomingCallUi() async {
|
static Future<void> dismissIncomingCallUi() async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
try {
|
||||||
|
await _androidPushChannel.invokeMethod<void>('dismissIncomingCalls');
|
||||||
|
return;
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _dismissViaCallKit();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> _dismissViaCallKit() async {
|
||||||
try {
|
try {
|
||||||
final activeCalls = await FlutterCallkitIncoming.activeCalls();
|
final activeCalls = await FlutterCallkitIncoming.activeCalls();
|
||||||
for (final call in activeCalls) {
|
for (final call in activeCalls) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: eatvio_app
|
name: eatvio_app
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
version: 1.3.32+121
|
version: 1.3.33+122
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
Reference in New Issue
Block a user