diff --git a/.idea/misc.xml b/.idea/misc.xml
index f4f6d798..f1fbd79c 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -6,4 +6,7 @@
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d8fad4a..4648f013 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+## 2.0.1
+
+* Fixed some bugs.
+* `Android` using Telecom Framework
+* Add `silenceEvents`
+* Add `normalHandle` props https://github.com/hiennguyen92/flutter_callkit_incoming/pull/403
+* Android add `textColor` props https://github.com/hiennguyen92/flutter_callkit_incoming/pull/398
+* Android invisible avatar for default https://github.com/hiennguyen92/flutter_callkit_incoming/pull/393
+* Add Method for call API when accept/decline/end/timeout
+
## 2.0.0+2
* Fixed some bugs.
diff --git a/README.md b/README.md
index 07fab3b0..a2679a3e 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
```
...
-
@@ -65,7 +65,7 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
* Import
```console
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
- ```
+ ```
* Received an incoming call
```dart
this._currentUuid = _uuid.v4();
@@ -94,6 +94,7 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
backgroundColor: '#0955fa',
backgroundUrl: 'https://i.pravatar.cc/500',
actionColor: '#4CAF50',
+ textColor: '#ffffff',
incomingCallNotificationChannelName: "Incoming Call",
missedCallNotificationChannelName: "Missed Call"
),
@@ -194,7 +195,7 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
//Example
d6a77ca80c5f09f87f353cdd328ec8d7d34e92eb108d046c91906f27f54949cd
-
+
```
Make sure using `SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken)` inside AppDelegate.swift (Example)
```swift
@@ -204,7 +205,7 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
//Save deviceToken to your server
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken)
}
-
+
func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
print("didInvalidatePushTokenFor")
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("")
@@ -263,7 +264,7 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
}
});
```
- * Call from Native (iOS/Android)
+ * Call from Native (iOS/Android)
```swift
//Swift iOS
@@ -274,12 +275,12 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
info["type"] = 1
//... set more data
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(flutter_callkit_incoming.Data(args: info), fromPushKit: true)
-
+
//please make sure call `completion()` at the end of the pushRegistry(......, completion: @escaping () -> Void)
// or `DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { completion() }`
// if you don't call completion() in pushRegistry(......, completion: @escaping () -> Void), there may be app crash by system when receiving voIP
```
-
+
```kotlin
//Kotlin/Java Android
FlutterCallkitIncomingPlugin.getInstance().showIncomingNotification(...)
@@ -295,7 +296,7 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
//... set more data
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
```
-
+
```objc
@@ -312,7 +313,7 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
//... set more data
[SwiftFlutterCallkitIncomingPlugin.sharedInstance showCallkitIncoming:data fromPushKit:YES];
```
-
+
```swift
@@ -325,6 +326,75 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
//Kotlin/Java Android
FlutterCallkitIncomingPlugin.getInstance().sendEventCustom(body: Map)
```
+ * 3.1 Call API when accept/decline/end/timeout
+ ```swift
+ //Appdelegate
+ ...
+ @UIApplicationMain
+ @objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CallkitIncomingAppDelegate {
+ ...
+
+ // Func Call api for Accept
+ func onAccept(_ call: Call) {
+ let json = ["action": "ACCEPT", "data": call.data.toJSON()] as [String: Any]
+ print("LOG: onAccept")
+ self.performRequest(parameters: json) { result in
+ switch result {
+ case .success(let data):
+ print("Received data: \(data)")
+
+ case .failure(let error):
+ print("Error: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ // Func Call API for Decline
+ func onDecline(_ call: Call) {
+ let json = ["action": "DECLINE", "data": call.data.toJSON()] as [String: Any]
+ print("LOG: onDecline")
+ self.performRequest(parameters: json) { result in
+ switch result {
+ case .success(let data):
+ print("Received data: \(data)")
+
+ case .failure(let error):
+ print("Error: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ func onEnd(_ call: Call) {
+ let json = ["action": "END", "data": call.data.toJSON()] as [String: Any]
+ print("LOG: onEnd")
+ self.performRequest(parameters: json) { result in
+ switch result {
+ case .success(let data):
+ print("Received data: \(data)")
+
+ case .failure(let error):
+ print("Error: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ func onTimeOut(_ call: Call) {
+ let json = ["action": "TIMEOUT", "data": call.data.toJSON()] as [String: Any]
+ print("LOG: onTimeOut")
+ self.performRequest(parameters: json) { result in
+ switch result {
+ case .success(let data):
+ print("Received data: \(data)")
+
+ case .failure(let error):
+ print("Error: \(error.localizedDescription)")
+ }
+ }
+ }
+ ...
+
+ ```
+ Please check full: Example
4. Properties
@@ -366,11 +436,12 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
| **`backgroundColor`** | Incoming call screen background color. | `#0955fa` |
| **`backgroundUrl`** | Using image background for Incoming call screen. example: http://... https://... or "assets/abc.png" | _None_ |
| **`actionColor`** | Color used in button/text on notification. | `#4CAF50` |
+ | **`textColor`** | Color used for the text in full screen notification. | `#ffffff` |
| **`incomingCallNotificationChannelName`** | Notification channel name of incoming call. | `Incoming call` |
| **`missedCallNotificationChannelName`** | Notification channel name of missed call. | `Missed call` |
-
+
* iOS
| Prop | Description | Default |
@@ -415,7 +486,7 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca
## :bulb: Demo
-1. Demo Illustration:
+1. Demo Illustration:
2. Image
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index dabdfcb3..fc2f0a8c 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -7,6 +7,7 @@
+
@@ -48,5 +49,15 @@
android:exported="true"
android:name="com.hiennv.flutter_callkit_incoming.CallkitSoundPlayerService"/>
+
+
+
+
+
+
diff --git a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/Call.kt b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/Call.kt
index 7c6989d3..ce217788 100644
--- a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/Call.kt
+++ b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/Call.kt
@@ -50,6 +50,8 @@ data class Data(val args: Map) {
var backgroundColor: String
@JsonProperty("backgroundUrl")
var backgroundUrl: String
+ @JsonProperty("textColor")
+ var textColor: String
@JsonProperty("actionColor")
var actionColor: String
@JsonProperty("incomingCallNotificationChannelName")
@@ -71,6 +73,13 @@ data class Data(val args: Map) {
@JsonProperty("isAccepted")
var isAccepted: Boolean = false
+ @JsonProperty("isOnHold")
+ var isOnHold: Boolean = (args["isOnHold"] as? Boolean) ?: false
+ @JsonProperty("audioRoute")
+ var audioRoute: Int = (args["audioRoute"] as? Int) ?: 1
+ @JsonProperty("isMuted")
+ var isMuted: Boolean = (args["isMuted"] as? Boolean) ?: false
+
init {
var android: Map? = args["android"] as? HashMap?
android = android ?: args
@@ -81,6 +90,7 @@ data class Data(val args: Map) {
backgroundColor = android["backgroundColor"] as? String ?: "#0955fa"
backgroundUrl = android["backgroundUrl"] as? String ?: ""
actionColor = android["actionColor"] as? String ?: "#4CAF50"
+ textColor = android["textColor"] as? String ?: "#ffffff"
incomingCallNotificationChannelName =
android["incomingCallNotificationChannelName"] as? String
missedCallNotificationChannelName = android["missedCallNotificationChannelName"] as? String
@@ -178,6 +188,7 @@ data class Data(val args: Map) {
CallkitConstants.EXTRA_CALLKIT_BACKGROUND_URL,
backgroundUrl
)
+ bundle.putString(CallkitConstants.EXTRA_CALLKIT_TEXT_COLOR, textColor)
bundle.putString(CallkitConstants.EXTRA_CALLKIT_ACTION_COLOR, actionColor)
bundle.putString(CallkitConstants.EXTRA_CALLKIT_ACTION_FROM, from)
bundle.putString(
@@ -256,6 +267,10 @@ data class Data(val args: Map) {
CallkitConstants.EXTRA_CALLKIT_ACTION_COLOR,
"#4CAF50"
)
+ data.textColor = bundle.getString(
+ CallkitConstants.EXTRA_CALLKIT_TEXT_COLOR,
+ "#FFFFFF"
+ )
data.from =
bundle.getString(CallkitConstants.EXTRA_CALLKIT_ACTION_FROM, "")
diff --git a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitConstants.kt b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitConstants.kt
index 2b4a61c1..9f50545d 100644
--- a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitConstants.kt
+++ b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitConstants.kt
@@ -20,6 +20,9 @@ object CallkitConstants {
"com.hiennv.flutter_callkit_incoming.ACTION_CALL_CALLBACK"
const val ACTION_CALL_CUSTOM =
"com.hiennv.flutter_callkit_incoming.ACTION_CALL_CUSTOM"
+ const val ACTION_CALL_AUDIO_STATE_CHANGE = "com.hiennv.flutter_callkit_incoming.ACTION_CALL_AUDIO_STATE_CHANGE"
+ const val ACTION_CALL_HELD = "com.hiennv.flutter_callkit_incoming.ACTION_CALL_HELD"
+ const val ACTION_CALL_UNHELD = "com.hiennv.flutter_callkit_incoming.ACTION_CALL_UNHELD"
const val EXTRA_CALLKIT_INCOMING_DATA = "EXTRA_CALLKIT_INCOMING_DATA"
@@ -52,10 +55,11 @@ object CallkitConstants {
const val EXTRA_CALLKIT_BACKGROUND_COLOR = "EXTRA_CALLKIT_BACKGROUND_COLOR"
const val EXTRA_CALLKIT_BACKGROUND_URL = "EXTRA_CALLKIT_BACKGROUND_URL"
const val EXTRA_CALLKIT_ACTION_COLOR = "EXTRA_CALLKIT_ACTION_COLOR"
+ const val EXTRA_CALLKIT_TEXT_COLOR = "EXTRA_CALLKIT_TEXT_COLOR"
const val EXTRA_CALLKIT_INCOMING_CALL_NOTIFICATION_CHANNEL_NAME =
"EXTRA_CALLKIT_INCOMING_CALL_NOTIFICATION_CHANNEL_NAME"
const val EXTRA_CALLKIT_MISSED_CALL_NOTIFICATION_CHANNEL_NAME =
"EXTRA_CALLKIT_MISSED_CALL_NOTIFICATION_CHANNEL_NAME"
const val EXTRA_CALLKIT_ACTION_FROM = "EXTRA_CALLKIT_ACTION_FROM"
-}
\ No newline at end of file
+}
diff --git a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitIncomingActivity.kt b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitIncomingActivity.kt
index 58916391..d21e949f 100644
--- a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitIncomingActivity.kt
+++ b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitIncomingActivity.kt
@@ -166,9 +166,16 @@ class CallkitIncomingActivity : Activity() {
val data = intent.extras?.getBundle(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA)
if (data == null) finish()
+ val textColor = data?.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_COLOR, "#ffffff")
tvNameCaller.text = data?.getString(CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, "")
tvNumber.text = data?.getString(CallkitConstants.EXTRA_CALLKIT_HANDLE, "")
+ try {
+ tvNameCaller.setTextColor(Color.parseColor(textColor))
+ tvNumber.setTextColor(Color.parseColor(textColor))
+ } catch (error: Exception) {
+ }
+
val isShowLogo = data?.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_SHOW_LOGO, false)
ivLogo.visibility = if (isShowLogo == true) View.VISIBLE else View.INVISIBLE
@@ -197,6 +204,12 @@ class CallkitIncomingActivity : Activity() {
val textDecline = data?.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_DECLINE, "")
tvDecline.text = if (TextUtils.isEmpty(textDecline)) getString(R.string.text_decline) else textDecline
+ try {
+ tvAccept.setTextColor(Color.parseColor(textColor))
+ tvDecline.setTextColor(Color.parseColor(textColor))
+ } catch (error: Exception) {
+ }
+
val backgroundColor = data?.getString(CallkitConstants.EXTRA_CALLKIT_BACKGROUND_COLOR, "#0955fa")
try {
ivBackground.setBackgroundColor(Color.parseColor(backgroundColor))
diff --git a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitIncomingBroadcastReceiver.kt b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitIncomingBroadcastReceiver.kt
index 52457203..8b528543 100644
--- a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitIncomingBroadcastReceiver.kt
+++ b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitIncomingBroadcastReceiver.kt
@@ -12,54 +12,67 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() {
companion object {
private const val TAG = "CallkitIncomingReceiver"
+ var silenceEvents = false
fun getIntent(context: Context, action: String, data: Bundle?) =
- Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
- this.action = "${context.packageName}.${action}"
- putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
- }
+ Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
+ this.action = "${context.packageName}.${action}"
+ putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
+ }
fun getIntentIncoming(context: Context, data: Bundle?) =
- Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
- action = "${context.packageName}.${CallkitConstants.ACTION_CALL_INCOMING}"
- putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
- }
+ Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
+ action = "${context.packageName}.${CallkitConstants.ACTION_CALL_INCOMING}"
+ putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
+ }
fun getIntentStart(context: Context, data: Bundle?) =
- Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
- action = "${context.packageName}.${CallkitConstants.ACTION_CALL_START}"
- putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
- }
+ Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
+ action = "${context.packageName}.${CallkitConstants.ACTION_CALL_START}"
+ putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
+ }
fun getIntentAccept(context: Context, data: Bundle?) =
- Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
- action = "${context.packageName}.${CallkitConstants.ACTION_CALL_ACCEPT}"
- putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
- }
+ Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
+ action = "${context.packageName}.${CallkitConstants.ACTION_CALL_ACCEPT}"
+ putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
+ }
fun getIntentDecline(context: Context, data: Bundle?) =
- Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
- action = "${context.packageName}.${CallkitConstants.ACTION_CALL_DECLINE}"
- putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
- }
+ Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
+ action = "${context.packageName}.${CallkitConstants.ACTION_CALL_DECLINE}"
+ putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
+ }
fun getIntentEnded(context: Context, data: Bundle?) =
- Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
- action = "${context.packageName}.${CallkitConstants.ACTION_CALL_ENDED}"
- putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
- }
+ Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
+ action = "${context.packageName}.${CallkitConstants.ACTION_CALL_ENDED}"
+ putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
+ }
fun getIntentTimeout(context: Context, data: Bundle?) =
- Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
- action = "${context.packageName}.${CallkitConstants.ACTION_CALL_TIMEOUT}"
- putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
- }
+ Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
+ action = "${context.packageName}.${CallkitConstants.ACTION_CALL_TIMEOUT}"
+ putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
+ }
fun getIntentCallback(context: Context, data: Bundle?) =
- Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
- action = "${context.packageName}.${CallkitConstants.ACTION_CALL_CALLBACK}"
- putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
- }
+ Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
+ action = "${context.packageName}.${CallkitConstants.ACTION_CALL_CALLBACK}"
+ putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
+ }
+
+ fun getIntentHeldByCell(context: Context, data: Bundle?) =
+ Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
+ action = "${context.packageName}.${CallkitConstants.ACTION_CALL_HELD}"
+ putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
+ }
+
+ fun getIntentUnHeldByCell(context: Context, data: Bundle?) =
+ Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {
+ action = "${context.packageName}.${CallkitConstants.ACTION_CALL_UNHELD}"
+ putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data)
+ }
}
@@ -76,7 +89,7 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() {
addCall(context, Data.fromBundle(data))
if (callkitNotificationManager.incomingChannelEnabled()) {
val soundPlayerServiceIntent =
- Intent(context, CallkitSoundPlayerService::class.java)
+ Intent(context, CallkitSoundPlayerService::class.java)
soundPlayerServiceIntent.putExtras(data)
context.startService(soundPlayerServiceIntent)
}
@@ -84,6 +97,7 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() {
Log.e(TAG, null, error)
}
}
+
"${context.packageName}.${CallkitConstants.ACTION_CALL_START}" -> {
try {
sendEventFlutter(CallkitConstants.ACTION_CALL_START, data)
@@ -92,6 +106,7 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() {
Log.e(TAG, null, error)
}
}
+
"${context.packageName}.${CallkitConstants.ACTION_CALL_ACCEPT}" -> {
try {
sendEventFlutter(CallkitConstants.ACTION_CALL_ACCEPT, data)
@@ -102,6 +117,7 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() {
Log.e(TAG, null, error)
}
}
+
"${context.packageName}.${CallkitConstants.ACTION_CALL_DECLINE}" -> {
try {
sendEventFlutter(CallkitConstants.ACTION_CALL_DECLINE, data)
@@ -112,6 +128,7 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() {
Log.e(TAG, null, error)
}
}
+
"${context.packageName}.${CallkitConstants.ACTION_CALL_ENDED}" -> {
try {
sendEventFlutter(CallkitConstants.ACTION_CALL_ENDED, data)
@@ -122,6 +139,7 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() {
Log.e(TAG, null, error)
}
}
+
"${context.packageName}.${CallkitConstants.ACTION_CALL_TIMEOUT}" -> {
try {
sendEventFlutter(CallkitConstants.ACTION_CALL_TIMEOUT, data)
@@ -134,6 +152,7 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() {
Log.e(TAG, null, error)
}
}
+
"${context.packageName}.${CallkitConstants.ACTION_CALL_CALLBACK}" -> {
try {
callkitNotificationManager.clearMissCallNotification(data)
@@ -150,45 +169,48 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() {
}
private fun sendEventFlutter(event: String, data: Bundle) {
+ if (silenceEvents) return
+
val android = mapOf(
- "isCustomNotification" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_NOTIFICATION, false),
- "isCustomSmallExNotification" to data.getBoolean(
- CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_SMALL_EX_NOTIFICATION,
- false
- ),
- "ringtonePath" to data.getString(CallkitConstants.EXTRA_CALLKIT_RINGTONE_PATH, ""),
- "backgroundColor" to data.getString(CallkitConstants.EXTRA_CALLKIT_BACKGROUND_COLOR, ""),
- "backgroundUrl" to data.getString(CallkitConstants.EXTRA_CALLKIT_BACKGROUND_URL, ""),
- "actionColor" to data.getString(CallkitConstants.EXTRA_CALLKIT_ACTION_COLOR, ""),
- "incomingCallNotificationChannelName" to data.getString(
- CallkitConstants.EXTRA_CALLKIT_INCOMING_CALL_NOTIFICATION_CHANNEL_NAME,
- ""
- ),
- "missedCallNotificationChannelName" to data.getString(
- CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_NOTIFICATION_CHANNEL_NAME,
- ""
- ),
+ "isCustomNotification" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_NOTIFICATION, false),
+ "isCustomSmallExNotification" to data.getBoolean(
+ CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_SMALL_EX_NOTIFICATION,
+ false
+ ),
+ "ringtonePath" to data.getString(CallkitConstants.EXTRA_CALLKIT_RINGTONE_PATH, ""),
+ "backgroundColor" to data.getString(CallkitConstants.EXTRA_CALLKIT_BACKGROUND_COLOR, ""),
+ "backgroundUrl" to data.getString(CallkitConstants.EXTRA_CALLKIT_BACKGROUND_URL, ""),
+ "actionColor" to data.getString(CallkitConstants.EXTRA_CALLKIT_ACTION_COLOR, ""),
+ "textColor" to data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_COLOR, ""),
+ "incomingCallNotificationChannelName" to data.getString(
+ CallkitConstants.EXTRA_CALLKIT_INCOMING_CALL_NOTIFICATION_CHANNEL_NAME,
+ ""
+ ),
+ "missedCallNotificationChannelName" to data.getString(
+ CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_NOTIFICATION_CHANNEL_NAME,
+ ""
+ ),
)
val notification = mapOf(
- "id" to data.getInt(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_ID),
- "showNotification" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_SHOW),
- "count" to data.getInt(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_COUNT),
- "subtitle" to data.getString(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_SUBTITLE),
- "callbackText" to data.getString(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_TEXT),
- "isShowCallback" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_SHOW),
+ "id" to data.getInt(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_ID),
+ "showNotification" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_SHOW),
+ "count" to data.getInt(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_COUNT),
+ "subtitle" to data.getString(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_SUBTITLE),
+ "callbackText" to data.getString(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_TEXT),
+ "isShowCallback" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_SHOW),
)
val forwardData = mapOf(
- "id" to data.getString(CallkitConstants.EXTRA_CALLKIT_ID, ""),
- "nameCaller" to data.getString(CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, ""),
- "avatar" to data.getString(CallkitConstants.EXTRA_CALLKIT_AVATAR, ""),
- "number" to data.getString(CallkitConstants.EXTRA_CALLKIT_HANDLE, ""),
- "type" to data.getInt(CallkitConstants.EXTRA_CALLKIT_TYPE, 0),
- "duration" to data.getLong(CallkitConstants.EXTRA_CALLKIT_DURATION, 0L),
- "textAccept" to data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_ACCEPT, ""),
- "textDecline" to data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_DECLINE, ""),
- "extra" to data.getSerializable(CallkitConstants.EXTRA_CALLKIT_EXTRA)!!,
- "missedCallNotification" to notification,
- "android" to android
+ "id" to data.getString(CallkitConstants.EXTRA_CALLKIT_ID, ""),
+ "nameCaller" to data.getString(CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, ""),
+ "avatar" to data.getString(CallkitConstants.EXTRA_CALLKIT_AVATAR, ""),
+ "number" to data.getString(CallkitConstants.EXTRA_CALLKIT_HANDLE, ""),
+ "type" to data.getInt(CallkitConstants.EXTRA_CALLKIT_TYPE, 0),
+ "duration" to data.getLong(CallkitConstants.EXTRA_CALLKIT_DURATION, 0L),
+ "textAccept" to data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_ACCEPT, ""),
+ "textDecline" to data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_DECLINE, ""),
+ "extra" to data.getSerializable(CallkitConstants.EXTRA_CALLKIT_EXTRA)!!,
+ "missedCallNotification" to notification,
+ "android" to android
)
FlutterCallkitIncomingPlugin.sendEvent(event, forwardData)
}
diff --git a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitSoundPlayerService.kt b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitSoundPlayerService.kt
index a4cbf13f..92d23667 100644
--- a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitSoundPlayerService.kt
+++ b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitSoundPlayerService.kt
@@ -1,10 +1,8 @@
package com.hiennv.flutter_callkit_incoming
-import android.annotation.SuppressLint
import android.app.Service
import android.content.Context
import android.content.Intent
-import android.content.res.AssetFileDescriptor
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.MediaPlayer
@@ -37,6 +35,9 @@ class CallkitSoundPlayerService : Service() {
mediaPlayer?.stop()
mediaPlayer?.release()
vibrator?.cancel()
+
+ mediaPlayer = null
+ vibrator = null
}
private fun prepare() {
diff --git a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/FlutterCallkitIncomingPlugin.kt b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/FlutterCallkitIncomingPlugin.kt
index f3db194c..1478a2d8 100644
--- a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/FlutterCallkitIncomingPlugin.kt
+++ b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/FlutterCallkitIncomingPlugin.kt
@@ -1,21 +1,16 @@
package com.hiennv.flutter_callkit_incoming
-import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
-import android.content.DialogInterface
import android.content.Intent
-import android.content.pm.PackageManager
-import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
-import android.provider.Settings
+import android.util.Log
import androidx.annotation.NonNull
-import androidx.appcompat.app.AlertDialog
-import androidx.core.app.ActivityCompat
import com.hiennv.flutter_callkit_incoming.Utils.Companion.reapCollection
+import com.hiennv.flutter_callkit_incoming.telecom.TelecomUtilities
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
@@ -34,6 +29,9 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
@SuppressLint("StaticFieldLeak")
private lateinit var instance: FlutterCallkitIncomingPlugin
+ @SuppressLint("StaticFieldLeak")
+ private lateinit var telecomUtilities: TelecomUtilities
+
public fun getInstance(): FlutterCallkitIncomingPlugin {
return instance
}
@@ -79,6 +77,9 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
val handler = EventCallbackHandler()
eventHandlers.add(WeakReference(handler))
events.setStreamHandler(handler)
+
+ telecomUtilities = TelecomUtilities(context)
+ TelecomUtilities.telecomUtilitiesSingleton = telecomUtilities
}
}
@@ -161,14 +162,34 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
data.toBundle()
)
)
+
+ // only report to telecom if it's a voice call
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ telecomUtilities.reportIncomingCall(data)
+ }
+
+ result.success("OK")
+ }
+
+ "showCallkitIncomingSilently" -> {
+ val data = Data(call.arguments() ?: HashMap())
+ data.from = "notification"
+
+ // we don't need to send a broadcast, we only need to report the data to telecom
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ telecomUtilities.reportIncomingCall(data)
+ }
+
result.success("OK")
}
+
"showMissCallNotification" -> {
val data = Data(call.arguments() ?: HashMap())
data.from = "notification"
callkitNotificationManager?.showMissCallNotification(data.toBundle())
result.success("OK")
}
+
"startCall" -> {
val data = Data(call.arguments() ?: HashMap())
context?.sendBroadcast(
@@ -177,8 +198,14 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
data.toBundle()
)
)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ telecomUtilities.startCall(data)
+ }
+
result.success("OK")
}
+
"muteCall" -> {
val map = buildMap {
val args = call.arguments
@@ -186,9 +213,16 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
putAll(args as Map)
}
}
- sendEvent(CallkitConstants.ACTION_CALL_TOGGLE_MUTE, map);
+ sendEvent(CallkitConstants.ACTION_CALL_TOGGLE_MUTE, map)
+
+ val data = Data(call.arguments() ?: HashMap())
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ telecomUtilities.muteCall(data)
+ }
+
result.success("OK")
}
+
"holdCall" -> {
val map = buildMap {
val args = call.arguments
@@ -196,12 +230,24 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
putAll(args as Map)
}
}
- sendEvent(CallkitConstants.ACTION_CALL_TOGGLE_HOLD, map);
+ sendEvent(CallkitConstants.ACTION_CALL_TOGGLE_HOLD, map)
+
+ val data = Data(call.arguments() ?: HashMap())
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (data.isOnHold) {
+ telecomUtilities.holdCall(data)
+ } else {
+ telecomUtilities.unHoldCall(data)
+ }
+ }
+
result.success("OK")
}
+
"isMuted" -> {
result.success(false)
}
+
"endCall" -> {
val data = Data(call.arguments() ?: HashMap())
context?.sendBroadcast(
@@ -210,11 +256,22 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
data.toBundle()
)
)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ telecomUtilities.endCall(data)
+ }
+
result.success("OK")
}
+
"callConnected" -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ telecomUtilities.acceptCall(Data(call.arguments() ?: HashMap()))
+ }
+
result.success("OK")
}
+
"endAllCalls" -> {
val calls = getDataActiveCalls(context)
calls.forEach {
@@ -235,14 +292,29 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
}
}
removeAllCalls(context)
+
+ //Additional safety net
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ telecomUtilities.endAllActiveCalls()
+ }
+
result.success("OK")
}
+
"activeCalls" -> {
result.success(getDataActiveCallsForFlutter(context))
}
+
"getDevicePushTokenVoIP" -> {
result.success("")
}
+
+ "silenceEvents" -> {
+ val silence = call.arguments as? Boolean ?: false
+ CallkitIncomingBroadcastReceiver.silenceEvents = silence
+ result.success("")
+ }
+
"requestNotificationPermission" -> {
val map = buildMap {
val args = call.arguments
@@ -252,6 +324,26 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
}
callkitNotificationManager?.requestNotificationPermission(activity, map)
}
+ // EDIT - clear the incoming notification/ring (after accept/decline/timeout)
+ "hideCallkitIncoming" -> {
+ val data = Data(call.arguments() ?: HashMap())
+ context?.stopService(Intent(context, CallkitSoundPlayerService::class.java))
+ callkitNotificationManager?.clearIncomingNotification(data.toBundle(), false)
+ }
+
+ "endNativeSubsystemOnly" -> {
+ val data = Data(call.arguments() ?: HashMap())
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ telecomUtilities.endCall(data)
+ }
+ }
+
+ "setAudioRoute" -> {
+ val data = Data(call.arguments() ?: HashMap())
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ telecomUtilities.setAudioRoute(data)
+ }
+ }
}
} catch (error: Exception) {
result.error("error", error.message, "")
@@ -278,7 +370,12 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
binding.addRequestPermissionsResultListener(this)
}
- override fun onDetachedFromActivity() {}
+ override fun onDetachedFromActivity() {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ Log.d("FlutterCallkitPlugin", "onDetachedFromActivity: called -- activity destroyed? ${activity?.isDestroyed}")
+ if (activity?.isDestroyed == true) telecomUtilities.endAllActiveCalls()
+ }
+ }
class EventCallbackHandler : EventChannel.StreamHandler {
@@ -309,5 +406,4 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA
}
-
}
diff --git a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/telecom/TelecomConnection.kt b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/telecom/TelecomConnection.kt
new file mode 100644
index 00000000..09ca3f84
--- /dev/null
+++ b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/telecom/TelecomConnection.kt
@@ -0,0 +1,199 @@
+package com.hiennv.flutter_callkit_incoming.telecom
+
+
+import android.content.Context
+import android.net.Uri
+import android.os.Bundle
+import android.telecom.CallAudioState
+import android.telecom.Connection
+import android.telecom.DisconnectCause
+import android.telecom.TelecomManager
+import android.util.Log
+import androidx.core.os.bundleOf
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.ACTION_CALL_ACCEPT
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.ACTION_CALL_AUDIO_STATE_CHANGE
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.ACTION_CALL_ENDED
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.ACTION_CALL_HELD
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.ACTION_CALL_UNHELD
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.EXTRA_CALLKIT_HANDLE
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.EXTRA_CALLKIT_ID
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.EXTRA_CALLKIT_NAME_CALLER
+import com.hiennv.flutter_callkit_incoming.CallkitIncomingBroadcastReceiver
+import com.hiennv.flutter_callkit_incoming.telecom.TelecomUtilities.Companion.androidToJsRouteMap
+import java.io.PrintWriter
+import java.io.StringWriter
+
+
+// REF https://developer.android.com/reference/android/telecom/Connection
+// the handle hashmap has the uuid under `EXTRA_CALLKIT_ID`
+class TelecomConnection internal constructor(private val context: Context, private val handle: HashMap) : Connection() {
+ init {
+ // previously, the caps and voip mode were set in two different places for incoming/outgoing connections
+ // moreover, the voip mode was set in the "onAnswer" method for incoming calls which caused the connection to be incorrectly set up if it was answered from the app UI
+ connectionCapabilities = PROPERTY_SELF_MANAGED or CAPABILITY_MUTE or CAPABILITY_HOLD or CAPABILITY_SUPPORT_HOLD
+ audioModeIsVoip = true
+
+ val number = handle[EXTRA_CALLKIT_HANDLE]
+ val name = handle[EXTRA_CALLKIT_NAME_CALLER]
+
+ if (number != null) setAddress(Uri.parse(number), TelecomManager.PRESENTATION_ALLOWED)
+ if (name != null && name != "") setCallerDisplayName(name, TelecomManager.PRESENTATION_ALLOWED)
+ }
+
+ // called when answered from bt device/car
+ override fun onAnswer() {
+ super.onAnswer()
+ TelecomUtilities.logToFile("[TelecomConnection] onAnswer called")
+
+ val uuid = handle[EXTRA_CALLKIT_ID] ?: ""
+ val data: Map = object : HashMap() {
+ init {
+ put("event", ACTION_CALL_ACCEPT)
+ put(EXTRA_CALLKIT_ID, uuid)
+ }
+ }
+ TelecomUtilities.logToFile("[TelecomConnection] On Answer data: $data")
+
+ context.sendBroadcast(CallkitIncomingBroadcastReceiver.getIntentAccept(context, bundleOf(*data.toList().toTypedArray())))
+
+ TelecomUtilities.logToFile("[TelecomConnection] onAnswer executed")
+ setActive()
+ }
+
+ override fun onAbort() {
+ super.onAbort()
+ TelecomUtilities.logToFile("[TelecomConnection] onAbort")
+ setDisconnected(DisconnectCause(DisconnectCause.REJECTED))
+ endCall()
+ TelecomUtilities.logToFile("[TelecomConnection] onAbort executed")
+ }
+
+ override fun onReject() {
+ super.onReject()
+ setDisconnected(DisconnectCause(DisconnectCause.REJECTED))
+ TelecomUtilities.logToFile("[TelecomConnection] onReject")
+ endCall()
+ TelecomUtilities.logToFile("[TelecomConnection] onReject executed")
+ }
+ override fun onDisconnect() {
+ super.onDisconnect()
+ TelecomUtilities.logToFile("[TelecomConnection] onDisconnect")
+ setDisconnected(DisconnectCause(DisconnectCause.REJECTED))
+ endCall()
+ }
+ public fun endCall() {
+ TelecomUtilities.logToFile("[TelecomConnection] Ending call - disconnectCause: $disconnectCause")
+ val uuid = handle[EXTRA_CALLKIT_ID] ?: ""
+ val data: Map = object : HashMap() {
+ init {
+ put("event", ACTION_CALL_ENDED)
+ put("disconnectCause", disconnectCause.toString())
+ put(EXTRA_CALLKIT_ID, uuid)
+ }
+ }
+ context.sendBroadcast(CallkitIncomingBroadcastReceiver.getIntentEnded(context, bundleOf(*data.toList().toTypedArray())))
+ try {
+ TelecomConnectionService.deinitConnection(handle[EXTRA_CALLKIT_ID] ?: "")
+
+ } catch (exception: Throwable) {
+ Log.e(TAG, "Handle map error", exception)
+
+ val stackTrace = StringWriter()
+ exception.printStackTrace(PrintWriter(stackTrace))
+
+ TelecomUtilities.logToFile("[TelecomUtilities] EXCEPTION reportIncomingCall -- $exception -- message: ${exception.message} -- stack: $stackTrace")
+ }
+
+ destroy()
+ }
+
+ override fun onHold() {
+ TelecomUtilities.logToFile("[TelecomConnection] On hold")
+ super.onHold()
+ //GF not needed
+
+ val uuid = handle[EXTRA_CALLKIT_ID] ?: ""
+ val data: Map = object : HashMap() {
+ init {
+ put("event", ACTION_CALL_HELD)
+ put(EXTRA_CALLKIT_ID, uuid)
+ put("args", 1)
+ }
+ }
+ context.sendBroadcast(CallkitIncomingBroadcastReceiver.getIntentHeldByCell(context, bundleOf(*data.toList().toTypedArray())))
+
+ setOnHold();
+
+ //context.sendBroadcast(CallkitIncomingBroadcastReceiver.getIntent(context, ACTION_CALL_HELD, bundleOf(*data.toList().toTypedArray())))
+
+
+
+ }
+ override fun onUnhold() {
+ super.onUnhold()
+ val uuid = handle[EXTRA_CALLKIT_ID] ?: ""
+ val data: Map = object : HashMap() {
+ init {
+ put("event", ACTION_CALL_UNHELD)
+ put(EXTRA_CALLKIT_ID, uuid)
+ put("args", 0)
+ }
+ }
+ //context.sendBroadcast(CallkitIncomingBroadcastReceiver.getIntentCallback(context, bundleOf(*data.toList().toTypedArray())))
+
+ context.sendBroadcast(CallkitIncomingBroadcastReceiver.getIntentUnHeldByCell(context, bundleOf(*data.toList().toTypedArray())))
+ TelecomConnectionService.setAllOthersOnHold(uuid)
+ setActive()
+ }
+
+ // dnc
+ override fun onPlayDtmfTone(dtmf: Char) {
+ TelecomUtilities.logToFile("[TelecomConnection] OnPlayDTMFTone")
+ }
+
+ // dnc - should be used to show the (fullscreen) notification for the user
+ override fun onShowIncomingCallUi() {
+ super.onShowIncomingCallUi()
+ TelecomUtilities.logToFile("[TelecomConnection] Show incoming call UI")
+ }
+ // dnc - should be used to silence the ringer when the user presses the volume down button
+ override fun onSilence() {
+ super.onSilence()
+ TelecomUtilities.logToFile("[TelecomConnection] TODO silence ringer")
+ }
+
+ // from inCallService (not used in self_managed)
+ override fun onCallEvent(event: String, extras: Bundle?) {
+ super.onCallEvent(event, extras)
+ TelecomUtilities.logToFile("[TelecomConnection] CALL EVENT: $event")
+ }
+
+ override fun onStateChanged(state: Int) {
+ super.onStateChanged(state)
+ TelecomUtilities.logToFile("[TelecomConnection] ON STATE CHANGED: $state")
+ // Toast.makeText(context, "onStateChanged $state", Toast.LENGTH_LONG).show()
+ }
+
+ // IMPORTANT (note: deprecated in Android 14 - API 34)
+ // this event triggers for both mute state and audio route
+ // actually it doesn't trigger for mute changes!!
+ override fun onCallAudioStateChanged(state: CallAudioState) {
+ super.onCallAudioStateChanged(state)
+ TelecomUtilities.logToFile("[TelecomConnection] On Call Audio State Changed -- route: ${state.route} -- is muted: ${state.isMuted}")
+
+ val uuid = handle[EXTRA_CALLKIT_ID] ?: ""
+ val data: Map = object : HashMap() {
+ init {
+ put("event", ACTION_CALL_AUDIO_STATE_CHANGE)
+ put(EXTRA_CALLKIT_ID, uuid)
+ put("args", androidToJsRouteMap[state.route] ?: 1) // TODO use a different key than "args"?
+ }
+ }
+
+ context.sendBroadcast(CallkitIncomingBroadcastReceiver.getIntent(context, ACTION_CALL_AUDIO_STATE_CHANGE, bundleOf(*data.toList().toTypedArray())))
+ }
+
+ companion object {
+ private const val TAG = "TelecomConnection"
+ }
+}
diff --git a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/telecom/TelecomConnectionService.kt b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/telecom/TelecomConnectionService.kt
new file mode 100644
index 00000000..8b3fe766
--- /dev/null
+++ b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/telecom/TelecomConnectionService.kt
@@ -0,0 +1,149 @@
+package com.hiennv.flutter_callkit_incoming.telecom
+
+import android.content.Context
+import android.os.Bundle
+import android.telecom.Connection
+import android.telecom.ConnectionRequest
+import android.telecom.ConnectionService
+import android.telecom.PhoneAccountHandle
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.EXTRA_CALLKIT_HANDLE
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.EXTRA_CALLKIT_ID
+import com.hiennv.flutter_callkit_incoming.CallkitConstants.EXTRA_CALLKIT_NAME_CALLER
+import java.util.UUID
+import android.util.Log
+
+// for now, I don't care about notifying anybody about connection creations/failures
+// the connection itself is supposed to do that?
+class TelecomConnectionService : ConnectionService() {
+
+
+
+ override fun onCreate() {
+ super.onCreate()
+ TelecomConnectionService.applicationContext = applicationContext
+ }
+
+ override fun onDestroy() {
+
+ try {
+ Log.d(TAG, "[TelecomConnectionService] onDestroy")
+ TelecomUtilities.logToFile("[TelecomConnectionService] onDestroy ")
+ TelecomUtilities.logToFile("[TelecomConnectionService] onDestroy - kill all calls ");
+
+ //We end all connections
+ for ((key, value) in currentConnections) {
+ value.endCall()
+
+ }
+ }
+ catch (er: Exception) {
+ TelecomUtilities.logToFile("EXCEPTION reportIncomingCall -- $er")
+ }
+
+ }
+
+ override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest): Connection {
+ TelecomUtilities.logToFile("[TelecomConnectionService] OnCreateIncomingConnection -- UUID: number:${request.extras.getString(EXTRA_CALLKIT_ID)}")
+
+ // to test global exception handling
+ // throw Exception("EXCEPTION from onCreateIncomingConnection")
+
+ val incomingCallConnection = createConnection(request)
+ incomingCallConnection.setRinging()
+ incomingCallConnection.setInitialized()
+
+ return incomingCallConnection
+ }
+
+ override fun onCreateIncomingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest) {
+ super.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request)
+ TelecomUtilities.logToFile("[TelecomConnectionService] OnCreateIncomingConnection FAILED")
+ }
+
+ override fun onCreateOutgoingConnection(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest): Connection {
+ val extras = request.extras
+ val number = request.address?.schemeSpecificPart ?: "Outbound Call"
+ val displayName = extras.getString(EXTRA_CALLKIT_NAME_CALLER)
+
+ TelecomUtilities.logToFile("[TelecomConnectionService] onCreateOutgoingConnection -- UUID: ${request.extras.getString(EXTRA_CALLKIT_ID)} number: $number, displayName:$displayName")
+
+ val outgoingCallConnection = createConnection(request)
+ outgoingCallConnection.setDialing()
+
+ TelecomUtilities.logToFile("[TelecomConnectionService] onCreateOutgoingConnection: dialing")
+
+ val uuid = outgoingCallConnection.extras.getString(EXTRA_CALLKIT_ID) ?: ""
+ setAllOthersOnHold(uuid)
+
+ return outgoingCallConnection
+ }
+
+ override fun onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle?, request: ConnectionRequest) {
+ super.onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount, request)
+ TelecomUtilities.logToFile("[TelecomConnectionService] OnCreateOutgoingConnectionFailed FAILED")
+ }
+
+ private fun createConnection(request: ConnectionRequest): Connection {
+ TelecomUtilities.logToFile("[TelecomConnectionService] createConnection -- UUID: ${request.extras.getString(EXTRA_CALLKIT_ID)}")
+
+ val extras = request.extras
+ if (extras.getString(EXTRA_CALLKIT_ID) == null) {
+ extras.putString(EXTRA_CALLKIT_ID, UUID.randomUUID().toString())
+ }
+
+ val extrasMap = bundleToMap(extras)
+ extrasMap[EXTRA_CALLKIT_HANDLE] = request.address?.toString() ?: "Callkit Incoming Call"
+ val connection = TelecomConnection(this, extrasMap)
+
+ connection.setInitializing()
+ connection.extras = extras
+ currentConnections[extras.getString(EXTRA_CALLKIT_ID)] = connection
+
+ return connection
+ }
+
+ private fun bundleToMap(extras: Bundle): HashMap {
+ val extrasMap = HashMap()
+ val keySet = extras.keySet()
+ val iterator: Iterator = keySet.iterator()
+ while (iterator.hasNext()) {
+ val key = iterator.next()
+ if (extras[key] != null) {
+ extrasMap[key] = extras[key].toString()
+ }
+ }
+ return extrasMap
+ }
+
+ companion object {
+
+ private const val TAG = "TelecomConnectionService"
+
+
+ var applicationContext: Context? = null
+
+ var currentConnections: MutableMap = HashMap()
+
+ fun getConnection(connectionId: String?): Connection? {
+ return if (currentConnections.containsKey(connectionId)) {
+ currentConnections[connectionId]
+ } else null
+ }
+
+ fun deinitConnection(connectionId: String) {
+ TelecomUtilities.logToFile("[TelecomConnectionService] deinitConnection: $connectionId")
+ if (currentConnections.containsKey(connectionId)) {
+ currentConnections.remove(connectionId)
+ }
+ }
+
+ // put all other calls on hold
+ fun setAllOthersOnHold(myUID: String?) {
+ for ((key, value) in currentConnections) {
+ if (!key.contentEquals(myUID)) {
+ value.onHold()
+ }
+ }
+ }
+ }
+}
diff --git a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/telecom/TelecomUtils.kt b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/telecom/TelecomUtils.kt
new file mode 100644
index 00000000..725ce97e
--- /dev/null
+++ b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/telecom/TelecomUtils.kt
@@ -0,0 +1,240 @@
+package com.hiennv.flutter_callkit_incoming.telecom
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.content.ComponentName
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.telecom.CallAudioState
+import android.telecom.Connection
+import android.telecom.PhoneAccount
+import android.telecom.PhoneAccountHandle
+import android.telecom.TelecomManager
+import android.telephony.TelephonyManager
+import android.util.Log
+import androidx.annotation.RequiresApi
+import com.hiennv.flutter_callkit_incoming.CallkitConstants
+import com.hiennv.flutter_callkit_incoming.Data
+import java.io.File
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+import java.time.ZoneOffset
+import java.util.UUID
+
+// the most important thing this does is registering the phone account
+@RequiresApi(Build.VERSION_CODES.M)
+class TelecomUtilities(private val applicationContext : Context) {
+
+ private lateinit var telecomManager: TelecomManager
+ private lateinit var handle: PhoneAccountHandle
+ private lateinit var telephonyManager: TelephonyManager
+
+ private var requiredPermissions: Array
+
+ init {
+ registerPhoneAccount(applicationContext)
+
+ requiredPermissions = arrayOf(Manifest.permission.READ_PHONE_STATE, Manifest.permission.CALL_PHONE, Manifest.permission.RECORD_AUDIO)
+ if(Build.VERSION.SDK_INT > 29){
+ requiredPermissions += Manifest.permission.READ_PHONE_NUMBERS
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ private fun registerPhoneAccount(appContext: Context) {
+
+ val cName = ComponentName(applicationContext, TelecomConnectionService::class.java)
+ val appName = getApplicationName(appContext)
+ handle = PhoneAccountHandle(cName, appName)
+
+ val identifier = appContext.resources.getIdentifier("ic_logo", "mipmap", appContext.packageName)
+ val icon = Icon.createWithResource(appContext, identifier)
+
+ val account = PhoneAccount.Builder(handle, appName)
+ .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+ .setIcon(icon)
+ .build()
+
+ telephonyManager = applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
+
+ telecomManager = applicationContext.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
+ telecomManager.registerPhoneAccount(account)
+
+ logToFile("[TelecomUtilities] REGISTERED PHONE ACCOUNT")
+ }
+
+ private fun getApplicationName(appContext: Context): String {
+ val applicationInfo = appContext.applicationInfo
+ val stringId = applicationInfo.labelRes
+ return if (stringId == 0) applicationInfo.nonLocalizedLabel.toString() else appContext.getString(stringId)
+ }
+
+ // incoming call
+ @RequiresApi(Build.VERSION_CODES.M)
+ fun reportIncomingCall(data: Data) {
+ try {
+ val extras = Bundle()
+
+ val uuid: String = data.id
+ extras.putString(CallkitConstants.EXTRA_CALLKIT_ID, uuid)
+
+ // dnc
+ val name: String = data.nameCaller
+ extras.putString(CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, name)
+ // visible in cars
+ val handleString: String = name // data.handle
+ val uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, name, null)
+ extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, uri)
+
+ logToFile("[TelecomUtilities] reportIncomingCall number: $handleString, uuid: $uuid")
+
+ telecomManager.addNewIncomingCall(handle, extras)
+
+ } catch (er: Exception) {
+ Log.e(TAG,"EXCEPTION reportIncomingCall -- $er", er)
+
+ val stackTrace = StringWriter()
+ er.printStackTrace(PrintWriter(stackTrace))
+
+ logToFile("[TelecomUtilities] EXCEPTION reportIncomingCall -- $er -- message: ${er.message} -- stack: $stackTrace")
+ }
+ }
+
+ // outgoing call
+ @RequiresApi(Build.VERSION_CODES.M)
+ @SuppressLint("MissingPermission")
+ fun startCall(data: Data) {
+ val extras = Bundle() // has the account handle
+ val callExtras = Bundle() // has the caller's name/number
+
+ val uuid = UUID.fromString(data.uuid)
+
+ val number : String = data.handle
+ val uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null)
+ callExtras.putString(CallkitConstants.EXTRA_CALLKIT_HANDLE, number)
+ callExtras.putString(CallkitConstants.EXTRA_CALLKIT_ID, uuid.toString())
+
+ logToFile("[TelecomUtilities] startCall -- number: $number")
+
+ extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle)
+ extras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras)
+ telecomManager.placeCall(uri, extras)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ fun endCall(data: Data) {
+ logToFile("[TelecomUtilities] endCall -- UUID: ${data.uuid}")
+
+ val uuid: String = data.uuid
+ val connection = TelecomConnectionService.getConnection(uuid)
+ connection?.onDisconnect()
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ fun holdCall(data: Data) {
+ logToFile("[TelecomUtilities] holdCall -- UUID = ${data.uuid} | hold = ${data.isOnHold}")
+ val connection = TelecomConnectionService.getConnection(data.uuid)
+
+ if (data.isOnHold) connection?.onHold()
+ else connection?.onUnhold()
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ fun unHoldCall(data: Data) {
+ logToFile("[TelecomUtilities] unHoldCall -- UUID = ${data.uuid} ")
+ val connection = TelecomConnectionService.getConnection(data.uuid)
+ connection?.onUnhold()
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun setAudioRoute(data: Data) {
+ val connection = TelecomConnectionService.getConnection(data.uuid)
+
+ logToFile("[TelecomUtilities] setAudioRoute -- UUID = ${data.uuid} | audioRoute = ${data.audioRoute}")
+
+ val route = jsToAndroidRouteMap[data.audioRoute] ?: return
+ connection?.setAudioRoute(route)
+
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ fun muteCall(data: Data) {
+ logToFile("[TelecomUtilities] muteCall -- UUID = ${data.uuid} | hold = ${data.isMuted}")
+ val uuid : String = data.uuid
+ val muted : Boolean = data.isMuted
+ val connection = TelecomConnectionService.getConnection(uuid) ?: return
+
+ val newAudioState = if (muted) {
+ CallAudioState(true, connection.callAudioState.route, connection.callAudioState.supportedRouteMask)
+ } else {
+ CallAudioState(false, connection.callAudioState.route, connection.callAudioState.supportedRouteMask)
+ }
+
+ connection.onCallAudioStateChanged(newAudioState)
+ }
+
+ fun acceptCall(data: Data) {
+ val uuid : String = data.uuid
+
+ val connection = TelecomConnectionService.getConnection(uuid)
+ logToFile("[TelecomUtilities] acceptCall -- UUID = $uuid connection exists? ${connection!=null}")
+
+ // avoid infinite loop by not calling onAnswer if the state isn't already ACTIVE
+ if (connection?.state != Connection.STATE_ACTIVE) connection?.onAnswer()
+ else logToFile("[TelecomUtilities] acceptCall -- UUID = $uuid is already active")
+
+ logToFile("[TelecomUtilities] acceptCall -- AUDIO ROUTE: ${connection?.callAudioState?.route?.toString()}")
+ }
+
+ fun endAllActiveCalls() {
+ Log.d(TAG, "endAllActiveCalls: ${TelecomConnectionService.currentConnections.size}")
+ TelecomConnectionService.currentConnections.forEach { (_, c) -> c.onDisconnect() }
+ }
+
+ companion object {
+ private const val TAG = "TelecomUtilities"
+
+ public var telecomUtilitiesSingleton :TelecomUtilities? = null
+
+
+ val androidToJsRouteMap = mapOf(
+ CallAudioState.ROUTE_EARPIECE to 1,
+ CallAudioState.ROUTE_BLUETOOTH to 2,
+ CallAudioState.ROUTE_WIRED_HEADSET to 3,
+ CallAudioState.ROUTE_SPEAKER to 4,
+ CallAudioState.ROUTE_WIRED_OR_EARPIECE to 5,
+ )
+
+ val jsToAndroidRouteMap = mapOf(
+ 1 to CallAudioState.ROUTE_EARPIECE,
+ 2 to CallAudioState.ROUTE_BLUETOOTH,
+ 3 to CallAudioState.ROUTE_WIRED_HEADSET,
+ 4 to CallAudioState.ROUTE_SPEAKER,
+ 5 to CallAudioState.ROUTE_WIRED_OR_EARPIECE,
+ )
+
+ private const val logToFile = false // log to file flag
+ fun logToFile(message: String) {
+ Log.d("CallkitTelecom", message)
+
+ val context = TelecomConnectionService.applicationContext ?: return
+
+ if (!logToFile) return
+ try {
+ val timestamp = LocalDateTime.now(ZoneOffset.UTC)
+ val path = "${context.cacheDir}/console_logs_${timestamp.format(DateTimeFormatter.ofPattern("yyyyMMdd"))}.txt"
+
+ val file = File(path)
+ file.appendText("${timestamp.format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"))} $message")
+
+ } catch (e: Exception) {
+ Log.e(TAG, e.message ?: "", e)
+ }
+ }
+ }
+}
diff --git a/android/src/main/res/layout-w600dp-land/activity_callkit_incoming.xml b/android/src/main/res/layout-w600dp-land/activity_callkit_incoming.xml
index 3e5e6d95..f5c6682b 100644
--- a/android/src/main/res/layout-w600dp-land/activity_callkit_incoming.xml
+++ b/android/src/main/res/layout-w600dp-land/activity_callkit_incoming.xml
@@ -48,6 +48,7 @@
android:id="@+id/ivAvatar"
android:layout_width="@dimen/size_avatar"
android:layout_height="@dimen/size_avatar"
+ android:visibility="invisible"
android:layout_centerInParent="true"
android:src="@drawable/ic_default_avatar"
app:civ_border_color="#80ffffff"
diff --git a/android/src/main/res/layout/activity_callkit_incoming.xml b/android/src/main/res/layout/activity_callkit_incoming.xml
index 70bf002f..19c06432 100644
--- a/android/src/main/res/layout/activity_callkit_incoming.xml
+++ b/android/src/main/res/layout/activity_callkit_incoming.xml
@@ -48,6 +48,7 @@
android:id="@+id/ivAvatar"
android:layout_width="@dimen/size_avatar"
android:layout_height="@dimen/size_avatar"
+ android:visibility="invisible"
android:layout_centerInParent="true"
android:src="@drawable/ic_default_avatar"
app:civ_border_color="#80ffffff"
diff --git a/android/src/main/res/layout/layout_custom_notification.xml b/android/src/main/res/layout/layout_custom_notification.xml
index 627ad2e0..6baf5af0 100644
--- a/android/src/main/res/layout/layout_custom_notification.xml
+++ b/android/src/main/res/layout/layout_custom_notification.xml
@@ -40,7 +40,7 @@
android:layout_height="@dimen/base_margin_x4_8"
android:scaleType="centerCrop"
android:src="@drawable/ic_default_avatar"
- android:visibility="visible" />
+ android:visibility="invisible" />
+ android:visibility="invisible" />
+ android:visibility="invisible" />
10.0)
- GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/Logger (~> 7.8)
- - FirebaseCoreInternal (10.13.0):
+ - FirebaseCoreInternal (10.19.0):
- "GoogleUtilities/NSData+zlib (~> 7.8)"
- - FirebaseInstallations (10.13.0):
+ - FirebaseInstallations (10.19.0):
- FirebaseCore (~> 10.0)
- GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/UserDefaults (~> 7.8)
@@ -36,32 +36,32 @@ PODS:
- flutter_callkit_incoming (0.0.1):
- CryptoSwift
- Flutter
- - GoogleDataTransport (9.2.5):
+ - GoogleDataTransport (9.3.0):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30910.0, >= 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
- - GoogleUtilities/AppDelegateSwizzler (7.11.5):
+ - GoogleUtilities/AppDelegateSwizzler (7.12.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- - GoogleUtilities/Environment (7.11.5):
+ - GoogleUtilities/Environment (7.12.0):
- PromisesObjC (< 3.0, >= 1.2)
- - GoogleUtilities/Logger (7.11.5):
+ - GoogleUtilities/Logger (7.12.0):
- GoogleUtilities/Environment
- - GoogleUtilities/Network (7.11.5):
+ - GoogleUtilities/Network (7.12.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- - "GoogleUtilities/NSData+zlib (7.11.5)"
- - GoogleUtilities/Reachability (7.11.5):
+ - "GoogleUtilities/NSData+zlib (7.12.0)"
+ - GoogleUtilities/Reachability (7.12.0):
- GoogleUtilities/Logger
- - GoogleUtilities/UserDefaults (7.11.5):
+ - GoogleUtilities/UserDefaults (7.12.0):
- GoogleUtilities/Logger
- - nanopb (2.30909.0):
- - nanopb/decode (= 2.30909.0)
- - nanopb/encode (= 2.30909.0)
- - nanopb/decode (2.30909.0)
- - nanopb/encode (2.30909.0)
+ - nanopb (2.30909.1):
+ - nanopb/decode (= 2.30909.1)
+ - nanopb/encode (= 2.30909.1)
+ - nanopb/decode (2.30909.1)
+ - nanopb/encode (2.30909.1)
- PromisesObjC (2.3.1)
DEPENDENCIES:
@@ -94,21 +94,21 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_callkit_incoming/ios"
SPEC CHECKSUMS:
- CryptoSwift: d3d18dc357932f7e6d580689e065cf1f176007c1
+ CryptoSwift: 52aaf3fce7337552863b1d952e408085f0e65030
Firebase: 0219acf760880eeec8ce479895bd7767466d9f81
firebase_core: 312d0d81b346ec20540822c8498e626d6918ef48
firebase_messaging: 8432ce73100171cab1707fad998a89590276bb4d
FirebaseCore: e317665b9d744727a97e623edbbed009320afdd7
- FirebaseCoreInternal: b342e37cd4f5b4454ec34308f073420e7920858e
- FirebaseInstallations: b28af1b9f997f1a799efe818c94695a3728c352f
+ FirebaseCoreInternal: b444828ea7cfd594fca83046b95db98a2be4f290
+ FirebaseInstallations: 033d199474164db20c8350736842a94fe717b960
FirebaseMessaging: ac9062bcc35ed56e15a0241d8fd317022499baf8
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_callkit_incoming: 417dd1b46541cdd5d855ad795ccbe97d1c18155e
- GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2
- GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084
- nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431
+ GoogleDataTransport: 57c22343ab29bc686febbf7cbb13bad167c2d8fe
+ GoogleUtilities: 0759d1a57ebb953965c2dfe0ba4c82e95ccc2e34
+ nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
-PODFILE CHECKSUM: 7368163408c647b7eb699d0d788ba6718e18fb8d
+PODFILE CHECKSUM: 505fe807fc9a2ba684f6436bd029b74c8430ab62
COCOAPODS: 1.12.1
diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift
index 84dc7c35..1eb1a24e 100644
--- a/example/ios/Runner/AppDelegate.swift
+++ b/example/ios/Runner/AppDelegate.swift
@@ -4,7 +4,7 @@ import Flutter
import flutter_callkit_incoming
@UIApplicationMain
-@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate {
+@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CallkitIncomingAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
@@ -75,10 +75,108 @@ import flutter_callkit_incoming
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
//Make sure call completion()
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
completion()
}
}
+ // Func Call api for Accept
+ func onAccept(_ call: Call) {
+ let json = ["action": "ACCEPT", "data": call.data.toJSON()] as [String: Any]
+ print("LOG: onAccept")
+ self.performRequest(parameters: json) { result in
+ switch result {
+ case .success(let data):
+ print("Received data: \(data)")
+
+ case .failure(let error):
+ print("Error: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ // Func Call API for Decline
+ func onDecline(_ call: Call) {
+ let json = ["action": "DECLINE", "data": call.data.toJSON()] as [String: Any]
+ print("LOG: onDecline")
+ self.performRequest(parameters: json) { result in
+ switch result {
+ case .success(let data):
+ print("Received data: \(data)")
+
+ case .failure(let error):
+ print("Error: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ func onEnd(_ call: Call) {
+ let json = ["action": "END", "data": call.data.toJSON()] as [String: Any]
+ print("LOG: onEnd")
+ self.performRequest(parameters: json) { result in
+ switch result {
+ case .success(let data):
+ print("Received data: \(data)")
+
+ case .failure(let error):
+ print("Error: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ func onTimeOut(_ call: Call) {
+ let json = ["action": "TIMEOUT", "data": call.data.toJSON()] as [String: Any]
+ print("LOG: onTimeOut")
+ self.performRequest(parameters: json) { result in
+ switch result {
+ case .success(let data):
+ print("Received data: \(data)")
+
+ case .failure(let error):
+ print("Error: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ func performRequest(parameters: [String: Any], completion: @escaping (Result) -> Void) {
+ if let url = URL(string: "https://webhook.site/e32a591f-0d17-469d-a70d-33e9f9d60727") {
+ var request = URLRequest(url: url)
+ request.httpMethod = "POST"
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
+ //Add header
+
+ do {
+ let jsonData = try JSONSerialization.data(withJSONObject: parameters, options: [])
+ request.httpBody = jsonData
+ } catch {
+ completion(.failure(error))
+ return
+ }
+
+ let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
+ if let error = error {
+ completion(.failure(error))
+ return
+ }
+
+ guard let data = data else {
+ completion(.failure(NSError(domain: "mobile.app", code: 0, userInfo: [NSLocalizedDescriptionKey: "Empty data"])))
+ return
+ }
+
+ do {
+ let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
+ completion(.success(jsonObject))
+ } catch {
+ completion(.failure(error))
+ }
+ }
+ task.resume()
+ } else {
+ completion(.failure(NSError(domain: "mobile.app", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
+ }
+ }
+
+
}
diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart
index 3d0b884e..8434412a 100644
--- a/example/lib/home_page.dart
+++ b/example/lib/home_page.dart
@@ -152,6 +152,7 @@ class HomePageState extends State {
backgroundColor: '#0955fa',
backgroundUrl: 'assets/test.png',
actionColor: '#4CAF50',
+ textColor: '#ffffff',
incomingCallNotificationChannelName: 'Incoming Call',
missedCallNotificationChannelName: 'Missed Call',
),
@@ -204,8 +205,7 @@ class HomePageState extends State {
}
Future getDevicePushTokenVoIP() async {
- var devicePushTokenVoIP =
- await FlutterCallkitIncoming.getDevicePushTokenVoIP();
+ var devicePushTokenVoIP = await FlutterCallkitIncoming.getDevicePushTokenVoIP();
print(devicePushTokenVoIP);
}
@@ -224,8 +224,7 @@ class HomePageState extends State {
case Event.actionCallAccept:
// TODO: accepted an incoming call
// TODO: show screen calling in Flutter
- NavigationService.instance
- .pushNamedIfNotCurrent(AppRoute.callingPage, args: event.body);
+ NavigationService.instance.pushNamedIfNotCurrent(AppRoute.callingPage, args: event.body);
break;
case Event.actionCallDecline:
// TODO: declined an incoming call
@@ -270,8 +269,7 @@ class HomePageState extends State {
//check with https://webhook.site/#!/2748bc41-8599-4093-b8ad-93fd328f1cd2
Future requestHttp(content) async {
- get(Uri.parse(
- 'https://webhook.site/2748bc41-8599-4093-b8ad-93fd328f1cd2?data=$content'));
+ get(Uri.parse('https://webhook.site/2748bc41-8599-4093-b8ad-93fd328f1cd2?data=$content'));
}
void onEvent(CallEvent event) {
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 9c473b1c..201d3be7 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -40,6 +40,7 @@ Future showCallkitIncoming(String uuid) async {
backgroundColor: '#0955fa',
backgroundUrl: 'assets/test.png',
actionColor: '#4CAF50',
+ textColor: '#ffffff',
),
ios: const IOSParams(
iconName: 'CallKitLogo',
@@ -107,8 +108,7 @@ class MyAppState extends State with WidgetsBindingObserver {
Future checkAndNavigationCallingPage() async {
var currentCall = await getCurrentCall();
if (currentCall != null) {
- NavigationService.instance
- .pushNamedIfNotCurrent(AppRoute.callingPage, args: currentCall);
+ NavigationService.instance.pushNamedIfNotCurrent(AppRoute.callingPage, args: currentCall);
}
}
@@ -132,8 +132,7 @@ class MyAppState extends State with WidgetsBindingObserver {
_firebaseMessaging = FirebaseMessaging.instance;
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
- print(
- 'Message title: ${message.notification?.title}, body: ${message.notification?.body}, data: ${message.data}');
+ print('Message title: ${message.notification?.title}, body: ${message.notification?.body}, data: ${message.data}');
_currentUuid = _uuid.v4();
showCallkitIncoming(_currentUuid!);
});
@@ -149,15 +148,12 @@ class MyAppState extends State with WidgetsBindingObserver {
onGenerateRoute: AppRoute.generateRoute,
initialRoute: AppRoute.homePage,
navigatorKey: NavigationService.instance.navigationKey,
- navigatorObservers: [
- NavigationService.instance.routeObserver
- ],
+ navigatorObservers: [NavigationService.instance.routeObserver],
);
}
Future getDevicePushTokenVoIP() async {
- var devicePushTokenVoIP =
- await FlutterCallkitIncoming.getDevicePushTokenVoIP();
+ var devicePushTokenVoIP = await FlutterCallkitIncoming.getDevicePushTokenVoIP();
print(devicePushTokenVoIP);
}
}
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 23350d9d..c261ffd7 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -45,10 +45,10 @@ packages:
dependency: transitive
description:
name: collection
- sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
+ sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev"
source: hosted
- version: "1.17.1"
+ version: "1.17.2"
crypto:
dependency: transitive
description:
@@ -124,7 +124,7 @@ packages:
path: ".."
relative: true
source: path
- version: "2.0.0+1"
+ version: "2.0.0+2"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -171,18 +171,18 @@ packages:
dependency: transitive
description:
name: matcher
- sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
+ sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
- version: "0.12.15"
+ version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
- sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
+ sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
- version: "0.2.0"
+ version: "0.5.0"
meta:
dependency: transitive
description:
@@ -216,10 +216,10 @@ packages:
dependency: transitive
description:
name: source_span
- sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
+ sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
- version: "1.9.1"
+ version: "1.10.0"
stack_trace:
dependency: transitive
description:
@@ -256,10 +256,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
+ sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev"
source: hosted
- version: "0.5.1"
+ version: "0.6.0"
typed_data:
dependency: transitive
description:
@@ -284,6 +284,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
+ web:
+ dependency: transitive
+ description:
+ name: web
+ sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.1.4-beta"
sdks:
- dart: ">=3.0.0-0 <4.0.0"
+ dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.3.0"
diff --git a/ios/Classes/Call.swift b/ios/Classes/Call.swift
index 8228547e..4908efda 100644
--- a/ios/Classes/Call.swift
+++ b/ios/Classes/Call.swift
@@ -10,11 +10,11 @@ import AVFoundation
public class Call: NSObject {
- let uuid: UUID
- let data: Data
- let isOutGoing: Bool
+ public var uuid: UUID
+ public var data: Data
+ public var isOutGoing: Bool
- var handle: String?
+ public var handle: String?
var stateDidChange: (() -> Void)?
var hasStartedConnectDidChange: (() -> Void)?
@@ -133,6 +133,7 @@ public class Call: NSObject {
@objc public var handle: String
@objc public var avatar: String
@objc public var type: Int
+ @objc public var normalHandle: Int
@objc public var duration: Int
@objc public var extra: NSDictionary
@@ -161,6 +162,7 @@ public class Call: NSObject {
self.handle = handle
self.avatar = ""
self.type = type
+ self.normalHandle = 0
self.duration = 30000
self.extra = [:]
self.iconName = "CallKitLogo"
@@ -196,6 +198,7 @@ public class Call: NSObject {
self.handle = args["handle"] as? String ?? ""
self.avatar = args["avatar"] as? String ?? ""
self.type = args["type"] as? Int ?? 0
+ self.normalHandle = args["normalHandle"] as? Int ?? 0
self.duration = args["duration"] as? Int ?? 30000
self.extra = args["extra"] as? NSDictionary ?? [:]
@@ -237,7 +240,7 @@ public class Call: NSObject {
}
}
- public func toJSON() -> [String: Any] {
+ open func toJSON() -> [String: Any] {
let ios: [String : Any] = [
"iconName": iconName,
"handleType": handleType,
@@ -264,6 +267,7 @@ public class Call: NSObject {
"handle": handle,
"avatar": avatar,
"type": type,
+ "normalHandle": normalHandle,
"duration": duration,
"extra": extra,
"ios": ios
@@ -272,7 +276,36 @@ public class Call: NSObject {
}
func getEncryptHandle() -> String {
- return String(format: "{\"nameCaller\":\"%@\", \"handle\":\"%@\"}", nameCaller, handle).encryptHandle()
+ if (normalHandle > 0) {
+ return handle
+ }
+ do {
+ var map: [String: Any] = [:]
+
+ map["nameCaller"] = nameCaller
+ map["handle"] = handle
+
+ var mapExtras = extra as? [String: Any]
+
+ if (mapExtras == nil) {
+ print("error casting dictionary to [String: Any]")
+ return String(format: "{\"nameCaller\":\"%@\", \"handle\":\"%@\"}", nameCaller, handle).encryptHandle()
+ }
+
+ for (key, value) in mapExtras! {
+ map[key] = value
+ }
+
+ let mapData = try JSONSerialization.data(withJSONObject: map, options: .prettyPrinted)
+
+ let mapString: String = String(data: mapData, encoding: .utf8) ?? ""
+
+ return mapString.encryptHandle()
+ } catch {
+ print("error encrypting call data")
+ return String(format: "{\"nameCaller\":\"%@\", \"handle\":\"%@\"}", nameCaller, handle).encryptHandle()
+ }
+
}
diff --git a/ios/Classes/CallkitIncomingAppDelegate.swift b/ios/Classes/CallkitIncomingAppDelegate.swift
new file mode 100644
index 00000000..c9aa5a86
--- /dev/null
+++ b/ios/Classes/CallkitIncomingAppDelegate.swift
@@ -0,0 +1,21 @@
+//
+// CallkitIncomingAppDelegate.swift
+// flutter_callkit_incoming
+//
+// Created by Hien Nguyen on 05/01/2024.
+//
+
+import Foundation
+
+
+public protocol CallkitIncomingAppDelegate : NSObjectProtocol {
+
+ func onAccept(_ call: Call);
+
+ func onDecline(_ call: Call);
+
+ func onEnd(_ call: Call);
+
+ func onTimeOut(_ call: Call);
+
+}
diff --git a/ios/Classes/StringUtils.swift b/ios/Classes/StringUtils.swift
index f0d9058c..164205f2 100644
--- a/ios/Classes/StringUtils.swift
+++ b/ios/Classes/StringUtils.swift
@@ -30,8 +30,7 @@ extension String {
guard let data = Foundation.Data(base64Encoded: self) else {
return ""
}
-
- return String(data: data, encoding: .utf8)!
+ return String(data: data, encoding: .utf8) ?? ""
}
func toBase64() -> String {
@@ -48,6 +47,11 @@ extension String {
}
public func getDecryptHandle() -> [String: Any] {
+ if (!self.isBase64Encoded()) {
+ var map: [String: Any] = [:]
+ map["handle"] = self
+ return map
+ }
if let data = self.decryptHandle().data(using: .utf8) {
do {
return try (JSONSerialization.jsonObject(with: data, options: []) as? [String: Any])!
@@ -58,4 +62,31 @@ extension String {
return [:]
}
+ public func getHandleType() -> String {
+ if (!self.isBase64Encoded()) {
+ if (!self.isPhoneNumber()) {
+ return "email"
+ } else {
+ return "number"
+ }
+ }
+ return "generic"
+ }
+
+ public func isBase64Encoded() -> Bool {
+ let value = self.fromBase64()
+ return !value.isEmpty
+ }
+
+ func isPhoneNumber() -> Bool {
+ let cleanedValue = self
+ .replacingOccurrences(of: "[+-]", with: "", options: .regularExpression)
+ .replacingOccurrences(of: "[ ]", with: "", options: .regularExpression)
+
+
+ let decimalCharacters = CharacterSet.decimalDigits
+ let characterSet = CharacterSet(charactersIn: cleanedValue)
+ return decimalCharacters.isSuperset(of: characterSet)
+ }
+
}
diff --git a/ios/Classes/SwiftFlutterCallkitIncomingPlugin.swift b/ios/Classes/SwiftFlutterCallkitIncomingPlugin.swift
index 11c04d93..97b3860c 100644
--- a/ios/Classes/SwiftFlutterCallkitIncomingPlugin.swift
+++ b/ios/Classes/SwiftFlutterCallkitIncomingPlugin.swift
@@ -35,14 +35,20 @@ public class SwiftFlutterCallkitIncomingPlugin: NSObject, FlutterPlugin, CXProvi
private var data: Data?
private var isFromPushKit: Bool = false
+ private var silenceEvents: Bool = false
private let devicePushTokenVoIP = "DevicePushTokenVoIP"
- private var answerAction: CXAnswerCallAction?
private func sendEvent(_ event: String, _ body: [String : Any?]?) {
- streamHandlers.reap().forEach { handler in
- handler?.send(event, body ?? [:])
+ if silenceEvents {
+ print(event, " silenced")
+ return
+ } else {
+ streamHandlers.reap().forEach { handler in
+ handler?.send(event, body ?? [:])
+ }
}
+
}
@objc public func sendEventCustom(_ event: String, body: NSDictionary?) {
@@ -182,8 +188,25 @@ public class SwiftFlutterCallkitIncomingPlugin: NSObject, FlutterPlugin, CXProvi
case "getDevicePushTokenVoIP":
result(self.getDevicePushTokenVoIP())
break;
- case "startCallIncoming":
- self.answerAction?.fulfill()
+ case "silenceEvents":
+ guard let silence = call.arguments as? Bool else {
+ result("OK")
+ return
+ }
+
+ self.silenceEvents = silence
+ result("OK")
+ break;
+ case "requestNotificationPermission":
+ result("OK")
+ break
+ case "hideCallkitIncoming":
+ result("OK")
+ break
+ case "endNativeSubsystemOnly":
+ result("OK")
+ break
+ case "setAudioRoute":
result("OK")
break
default:
@@ -344,7 +367,13 @@ public class SwiftFlutterCallkitIncomingPlugin: NSObject, FlutterPlugin, CXProvi
func callEndTimeout(_ data: Data) {
self.saveEndCall(data.uuid, 3)
+ guard let call = self.callManager.callWithUUID(uuid: UUID(uuidString: data.uuid)!) else {
+ return
+ }
sendEvent(SwiftFlutterCallkitIncomingPlugin.ACTION_CALL_TIMEOUT, data.toJSON())
+ if let appDelegate = UIApplication.shared.delegate as? CallkitIncomingAppDelegate {
+ appDelegate.onTimeOut(call)
+ }
}
func getHandleType(_ handleType: String?) -> CXHandle.HandleType {
@@ -488,6 +517,7 @@ public class SwiftFlutterCallkitIncomingPlugin: NSObject, FlutterPlugin, CXProvi
action.fail()
return
}
+ self.configurAudioSession()
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1200)) {
self.configurAudioSession()
}
@@ -495,8 +525,11 @@ public class SwiftFlutterCallkitIncomingPlugin: NSObject, FlutterPlugin, CXProvi
self?.sharedProvider?.reportOutgoingCall(with: call.uuid, connectedAt: call.connectedData)
}
self.answerCall = call
- self.answerAction = action
sendEvent(SwiftFlutterCallkitIncomingPlugin.ACTION_CALL_ACCEPT, self.data?.toJSON())
+ if let appDelegate = UIApplication.shared.delegate as? CallkitIncomingAppDelegate {
+ appDelegate.onAccept(call)
+ }
+ action.fulfill()
}
@@ -514,11 +547,15 @@ public class SwiftFlutterCallkitIncomingPlugin: NSObject, FlutterPlugin, CXProvi
self.callManager.removeCall(call)
if (self.answerCall == nil && self.outgoingCall == nil) {
sendEvent(SwiftFlutterCallkitIncomingPlugin.ACTION_CALL_DECLINE, self.data?.toJSON())
- DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
- action.fulfill()
+ if let appDelegate = UIApplication.shared.delegate as? CallkitIncomingAppDelegate {
+ appDelegate.onDecline(call)
}
+ action.fulfill()
}else {
sendEvent(SwiftFlutterCallkitIncomingPlugin.ACTION_CALL_ENDED, call.data.toJSON())
+ if let appDelegate = UIApplication.shared.delegate as? CallkitIncomingAppDelegate {
+ appDelegate.onEnd(call)
+ }
action.fulfill()
}
}
@@ -566,7 +603,15 @@ public class SwiftFlutterCallkitIncomingPlugin: NSObject, FlutterPlugin, CXProvi
public func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
+ guard let call = self.callManager.callWithUUID(uuid: action.uuid) else {
+ action.fail()
+ return
+ }
sendEvent(SwiftFlutterCallkitIncomingPlugin.ACTION_CALL_TIMEOUT, self.data?.toJSON())
+ if let appDelegate = UIApplication.shared.delegate as? CallkitIncomingAppDelegate {
+ appDelegate.onTimeOut(call)
+ }
+ action.fulfill()
}
public func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
diff --git a/lib/entities/android_params.dart b/lib/entities/android_params.dart
index a47d3183..9524e113 100644
--- a/lib/entities/android_params.dart
+++ b/lib/entities/android_params.dart
@@ -13,6 +13,7 @@ class AndroidParams {
this.backgroundColor,
this.backgroundUrl,
this.actionColor,
+ this.textColor,
this.incomingCallNotificationChannelName,
this.missedCallNotificationChannelName,
});
@@ -38,14 +39,16 @@ class AndroidParams {
/// Color used in button/text on notification.
final String? actionColor;
+ /// Color used for the text in the full screen notification
+ final String? textColor;
+
/// Notification channel name of incoming call.
final String? incomingCallNotificationChannelName;
/// Notification channel name of missed call.
final String? missedCallNotificationChannelName;
- factory AndroidParams.fromJson(Map json) =>
- _$AndroidParamsFromJson(json);
+ factory AndroidParams.fromJson(Map json) => _$AndroidParamsFromJson(json);
Map toJson() => _$AndroidParamsToJson(this);
}
diff --git a/lib/entities/android_params.g.dart b/lib/entities/android_params.g.dart
index 8a1b1b00..bc3b7bda 100644
--- a/lib/entities/android_params.g.dart
+++ b/lib/entities/android_params.g.dart
@@ -15,6 +15,7 @@ AndroidParams _$AndroidParamsFromJson(Map json) =>
backgroundColor: json['backgroundColor'] as String?,
backgroundUrl: json['backgroundUrl'] as String?,
actionColor: json['actionColor'] as String?,
+ textColor: json['textColor'] as String?,
incomingCallNotificationChannelName:
json['incomingCallNotificationChannelName'] as String?,
missedCallNotificationChannelName:
@@ -30,6 +31,7 @@ Map _$AndroidParamsToJson(AndroidParams instance) =>
'backgroundColor': instance.backgroundColor,
'backgroundUrl': instance.backgroundUrl,
'actionColor': instance.actionColor,
+ 'textColor': instance.textColor,
'incomingCallNotificationChannelName':
instance.incomingCallNotificationChannelName,
'missedCallNotificationChannelName':
diff --git a/lib/entities/call_kit_params.dart b/lib/entities/call_kit_params.dart
index 824af512..e0e4e4f7 100644
--- a/lib/entities/call_kit_params.dart
+++ b/lib/entities/call_kit_params.dart
@@ -16,6 +16,7 @@ class CallKitParams {
this.avatar,
this.handle,
this.type,
+ this.normalHandle,
this.duration,
this.textAccept,
this.textDecline,
@@ -32,6 +33,7 @@ class CallKitParams {
final String? avatar;
final String? handle;
final int? type;
+ final int? normalHandle;
final int? duration;
final String? textAccept;
final String? textDecline;
diff --git a/lib/entities/call_kit_params.g.dart b/lib/entities/call_kit_params.g.dart
index 84351895..a2774cc1 100644
--- a/lib/entities/call_kit_params.g.dart
+++ b/lib/entities/call_kit_params.g.dart
@@ -14,6 +14,7 @@ CallKitParams _$CallKitParamsFromJson(Map json) =>
avatar: json['avatar'] as String?,
handle: json['handle'] as String?,
type: json['type'] as int?,
+ normalHandle: json['normalHandle'] as int?,
duration: json['duration'] as int?,
textAccept: json['textAccept'] as String?,
textDecline: json['textDecline'] as String?,
@@ -39,6 +40,7 @@ Map _$CallKitParamsToJson(CallKitParams instance) =>
'avatar': instance.avatar,
'handle': instance.handle,
'type': instance.type,
+ 'normalHandle': instance.normalHandle,
'duration': instance.duration,
'textAccept': instance.textAccept,
'textDecline': instance.textDecline,
diff --git a/lib/flutter_callkit_incoming.dart b/lib/flutter_callkit_incoming.dart
index a58d40c3..064d8e9c 100644
--- a/lib/flutter_callkit_incoming.dart
+++ b/lib/flutter_callkit_incoming.dart
@@ -111,12 +111,14 @@ class FlutterCallkitIncoming {
return await _channel.invokeMethod("getDevicePushTokenVoIP");
}
+ /// Silence CallKit events
+ static Future silenceEvents() async {
+ return await _channel.invokeMethod("silenceEvents", true);
+ }
- /// Start incoming call
- /// On iOS: start connection timer
- /// On Android: not implemented
- static Future startIncomingCall() async {
- await _channel.invokeMethod("startCallIncoming");
+ /// Unsilence CallKit events
+ static Future unsilenceEvents() async {
+ return await _channel.invokeMethod("silenceEvents", false);
}
/// Request permisstion show notification for Android(13)
diff --git a/pubspec.yaml b/pubspec.yaml
index d92cd618..06d0ac0f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: flutter_callkit_incoming
description: Flutter Callkit Incoming to show callkit screen in your Flutter app.
-version: 2.0.0+2
+version: 2.0.1
homepage: https://github.com/hiennguyen92/flutter_callkit_incoming
repository: https://github.com/hiennguyen92/flutter_callkit_incoming
issue_tracker: https://github.com/hiennguyen92/flutter_callkit_incoming/issues