diff --git a/CHANGELOG.md b/CHANGELOG.md index ab02bbaa..694d4b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.0.4+2 +* add func `requestFullIntentPermission` (Android 14+) thank @Spyspyspy https://github.com/hiennguyen92/flutter_callkit_incoming/pull/584 +* set Notification call style (Android) thank @AAkira https://github.com/hiennguyen92/flutter_callkit_incoming/pull/553 +* Many other issues + 1. add prop `accepted` in activeCalls (iOS) thank @vasilich6107 + 2. + ## 2.0.4+1 * Removed `Telecom Framework` (Android) diff --git a/README.md b/README.md index caa54daf..1a40e5fe 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,12 @@ Our top sponsors are shown below! }); ``` + * request permission for full intent Notification/full screen locked screen Android 14+ + For Android 14+, please `requestFullIntentPermission` + ```dart + await FlutterCallkitIncoming.requestFullIntentPermission(); + ``` + * Show miss call notification ```dart this._currentUuid = _uuid.v4(); @@ -504,7 +510,7 @@ Our top sponsors are shown below! | **`incomingCallNotificationChannelName`** | Notification channel name of incoming call. | `Incoming call` | | **`missedCallNotificationChannelName`** | Notification channel name of missed call. | `Missed call` | | **`isShowCallID`** | Show call id app inside full screen/notification. | false | - | **`isShowFullLockedScreen`** | Show full screen on Locked Screen. | true | + | **`isShowFullLockedScreen`** | Show full screen on Locked Screen(please make sure call `requestFullIntentPermission` for android 14+). | true |
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 cd17cb31..4d4006e5 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 @@ -85,6 +85,11 @@ data class Data(val args: Map) { @JsonProperty("isShowFullLockedScreen") var isShowFullLockedScreen: Boolean = true + @JsonProperty("isImportant") + var isImportant: Boolean = false + @JsonProperty("isBot") + var isBot: Boolean = false + init { var android: Map? = args["android"] as? HashMap? android = android ?: args @@ -101,6 +106,8 @@ data class Data(val args: Map) { android["incomingCallNotificationChannelName"] as? String missedCallNotificationChannelName = android["missedCallNotificationChannelName"] as? String isShowFullLockedScreen = android["isShowFullLockedScreen"] as? Boolean ?: true + isImportant = android["isImportant"] as? Boolean ?: false + isBot = android["isBot"] as? Boolean ?: false val missedNotification: Map? = args["missedCallNotification"] as? Map? @@ -214,6 +221,14 @@ data class Data(val args: Map) { CallkitConstants.EXTRA_CALLKIT_IS_SHOW_FULL_LOCKED_SCREEN, isShowFullLockedScreen ) + bundle.putBoolean( + CallkitConstants.EXTRA_CALLKIT_IS_IMPORTANT, + isImportant, + ) + bundle.putBoolean( + CallkitConstants.EXTRA_CALLKIT_IS_BOT, + isBot, + ) return bundle } @@ -237,6 +252,10 @@ data class Data(val args: Map) { bundle.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_ACCEPT, "") data.textDecline = bundle.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_DECLINE, "") + data.isImportant = + bundle.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_IMPORTANT, false) + data.isBot = + bundle.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_BOT, false) data.missedNotificationId = bundle.getInt(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_ID) 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 2e07d360..ff8f0fa5 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 @@ -65,4 +65,6 @@ object CallkitConstants { const val EXTRA_CALLKIT_ACTION_FROM = "EXTRA_CALLKIT_ACTION_FROM" const val EXTRA_CALLKIT_IS_SHOW_FULL_LOCKED_SCREEN = "EXTRA_CALLKIT_IS_SHOW_FULL_LOCKED_SCREEN" + const val EXTRA_CALLKIT_IS_IMPORTANT = "EXTRA_CALLKIT_IS_IMPORTANT" + const val EXTRA_CALLKIT_IS_BOT = "EXTRA_CALLKIT_IS_BOT" } 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 8b528543..74abe1e2 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 @@ -15,64 +15,64 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() { 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) - } + 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) - } + Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply { + action = "${context.packageName}.${CallkitConstants.ACTION_CALL_UNHELD}" + putExtra(CallkitConstants.EXTRA_CALLKIT_INCOMING_DATA, data) + } } @@ -190,6 +190,8 @@ class CallkitIncomingBroadcastReceiver : BroadcastReceiver() { CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_NOTIFICATION_CHANNEL_NAME, "" ), + "isImportant" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_IMPORTANT, false), + "isBot" to data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_BOT, false), ) val notification = mapOf( "id" to data.getInt(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_ID), diff --git a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitNotificationManager.kt b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitNotificationManager.kt index 1c9388a0..ca3117be 100644 --- a/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitNotificationManager.kt +++ b/android/src/main/kotlin/com/hiennv/flutter_callkit_incoming/CallkitNotificationManager.kt @@ -22,13 +22,13 @@ import android.os.Handler import android.os.Looper import android.provider.Settings import android.text.TextUtils -import android.util.Log import android.view.View import android.widget.RemoteViews import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.app.Person import com.hiennv.flutter_callkit_incoming.widgets.CircleTransform import com.squareup.picasso.OkHttp3Downloader import com.squareup.picasso.Picasso @@ -172,6 +172,12 @@ class CallkitNotificationManager(private val context: Context) { notificationBuilder.setCustomBigContentView(notificationViews) notificationBuilder.setCustomHeadsUpContentView(notificationSmallViews) } else { + notificationBuilder.setContentText( + data.getString( + CallkitConstants.EXTRA_CALLKIT_HANDLE, + "" + ) + ) val avatarUrl = data.getString(CallkitConstants.EXTRA_CALLKIT_AVATAR, "") if (avatarUrl != null && avatarUrl.isNotEmpty()) { val headers = @@ -179,32 +185,40 @@ class CallkitNotificationManager(private val context: Context) { getPicassoInstance(context, headers).load(avatarUrl) .into(targetLoadAvatarDefault) } - notificationBuilder.setContentTitle( - data.getString( - CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, - "" - ) - ) - notificationBuilder.setContentText( - data.getString( - CallkitConstants.EXTRA_CALLKIT_HANDLE, - "" + val caller = data.getString(CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, "") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + val person = Person.Builder() + .setName(caller) + .setImportant( + data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_IMPORTANT, false) + ) + .setBot(data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_BOT, false)) + .build() + notificationBuilder.setStyle( + NotificationCompat.CallStyle.forIncomingCall( + person, + getDeclinePendingIntent(notificationId, data), + getAcceptPendingIntent(notificationId, data), + ) + .setIsVideo(typeCall > 0) ) - ) - val textDecline = data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_DECLINE, "") - val declineAction: NotificationCompat.Action = NotificationCompat.Action.Builder( - R.drawable.ic_decline, - if (TextUtils.isEmpty(textDecline)) context.getString(R.string.text_decline) else textDecline, - getDeclinePendingIntent(notificationId, data) - ).build() - notificationBuilder.addAction(declineAction) - val textAccept = data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_ACCEPT, "") - val acceptAction: NotificationCompat.Action = NotificationCompat.Action.Builder( - R.drawable.ic_accept, - if (TextUtils.isEmpty(textDecline)) context.getString(R.string.text_accept) else textAccept, - getAcceptPendingIntent(notificationId, data) - ).build() - notificationBuilder.addAction(acceptAction) + } else { + notificationBuilder.setContentTitle(caller) + val textDecline = data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_DECLINE, "") + val declineAction: NotificationCompat.Action = NotificationCompat.Action.Builder( + R.drawable.ic_decline, + if (TextUtils.isEmpty(textDecline)) context.getString(R.string.text_decline) else textDecline, + getDeclinePendingIntent(notificationId, data) + ).build() + notificationBuilder.addAction(declineAction) + val textAccept = data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_ACCEPT, "") + val acceptAction: NotificationCompat.Action = NotificationCompat.Action.Builder( + R.drawable.ic_accept, + if (TextUtils.isEmpty(textDecline)) context.getString(R.string.text_accept) else textAccept, + getAcceptPendingIntent(notificationId, data) + ).build() + notificationBuilder.addAction(acceptAction) + } } val notification = notificationBuilder.build() notification.flags = Notification.FLAG_INSISTENT @@ -556,6 +570,15 @@ class CallkitNotificationManager(private val context: Context) { } } + fun requestFullIntentPermission(activity: Activity?) { + if (Build.VERSION.SDK_INT > 33) { + val intent = Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT).apply { + data = Uri.fromParts("package", activity?.packageName, null) + } + activity?.startActivity(intent) + } + } + fun onRequestPermissionsResult(activity: Activity?, requestCode: Int, grantResults: IntArray) { when (requestCode) { PERMISSION_NOTIFICATION_REQUEST_CODE -> { 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 9884c468..2e085b98 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 @@ -276,6 +276,9 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA } callkitNotificationManager?.requestNotificationPermission(activity, map) } + "requestFullIntentPermission" -> { + callkitNotificationManager?.requestFullIntentPermission(activity) + } // EDIT - clear the incoming notification/ring (after accept/decline/timeout) "hideCallkitIncoming" -> { val data = Data(call.arguments() ?: HashMap()) diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart index abf48f61..40817855 100644 --- a/example/lib/home_page.dart +++ b/example/lib/home_page.dart @@ -160,6 +160,8 @@ class HomePageState extends State { textColor: '#ffffff', incomingCallNotificationChannelName: 'Incoming Call', missedCallNotificationChannelName: 'Missed Call', + isImportant: true, + isBot: false, ), ios: const IOSParams( iconName: 'CallKitLogo', diff --git a/ios/Classes/CallManager.swift b/ios/Classes/CallManager.swift index 7883e74c..9760a139 100644 --- a/ios/Classes/CallManager.swift +++ b/ios/Classes/CallManager.swift @@ -66,6 +66,17 @@ class CallManager: NSObject { func connectedCall(call: Call) { let callItem = self.callWithUUID(uuid: call.uuid) callItem?.connectedCall(completion: nil) + + let answerAction = CXAnswerCallAction(call: call.uuid) + let transaction = CXTransaction(action: answerAction) + + callController.request(transaction) { error in + if let error = error { + print("Error answering call: \(error.localizedDescription)") + } else { + // Call successfully answered + } + } } func endCallAlls() { @@ -84,7 +95,8 @@ class CallManager: NSObject { for call in calls { let callItem = self.callWithUUID(uuid: call.uuid) if(callItem != nil){ - let item: [String: Any] = callItem!.data.toJSON() + var item: [String: Any] = callItem!.data.toJSON() + item["accepted"] = callItem?.hasConnected json.append(item) }else { let item: [String: String] = ["id": call.uuid.uuidString] diff --git a/ios/Classes/SwiftFlutterCallkitIncomingPlugin.swift b/ios/Classes/SwiftFlutterCallkitIncomingPlugin.swift index a487d91d..979df52a 100644 --- a/ios/Classes/SwiftFlutterCallkitIncomingPlugin.swift +++ b/ios/Classes/SwiftFlutterCallkitIncomingPlugin.swift @@ -200,6 +200,9 @@ public class SwiftFlutterCallkitIncomingPlugin: NSObject, FlutterPlugin, CXProvi case "requestNotificationPermission": result("OK") break + case "requestFullIntentPermission": + result("OK") + break case "hideCallkitIncoming": result("OK") break diff --git a/lib/entities/android_params.dart b/lib/entities/android_params.dart index c4cb2f7f..653c9a34 100644 --- a/lib/entities/android_params.dart +++ b/lib/entities/android_params.dart @@ -18,6 +18,8 @@ class AndroidParams { this.incomingCallNotificationChannelName, this.missedCallNotificationChannelName, this.isShowFullLockedScreen, + this.isImportant, + this.isBot, }); /// Using custom notifications. @@ -56,6 +58,14 @@ class AndroidParams { /// Show full locked screen. final bool? isShowFullLockedScreen; + /// Caller is important to the user of this device with regards to how frequently they interact. + /// https://developer.android.com/reference/androidx/core/app/Person#isImportant() + final bool? isImportant; + + /// Used primarily to identify automated tooling. + /// https://developer.android.com/reference/androidx/core/app/Person#isBot() + final bool? isBot; + factory AndroidParams.fromJson(Map json) => _$AndroidParamsFromJson(json); diff --git a/lib/entities/android_params.g.dart b/lib/entities/android_params.g.dart index 0df21f1c..aa36bfa9 100644 --- a/lib/entities/android_params.g.dart +++ b/lib/entities/android_params.g.dart @@ -22,6 +22,8 @@ AndroidParams _$AndroidParamsFromJson(Map json) => missedCallNotificationChannelName: json['missedCallNotificationChannelName'] as String?, isShowFullLockedScreen: json['isShowFullLockedScreen'] as bool?, + isImportant: json['isImportant'] as bool?, + isBot: json['isBot'] as bool?, ); Map _$AndroidParamsToJson(AndroidParams instance) => @@ -40,4 +42,6 @@ Map _$AndroidParamsToJson(AndroidParams instance) => 'missedCallNotificationChannelName': instance.missedCallNotificationChannelName, 'isShowFullLockedScreen': instance.isShowFullLockedScreen, + 'isImportant': instance.isImportant, + 'isBot': instance.isBot, }; diff --git a/lib/flutter_callkit_incoming.dart b/lib/flutter_callkit_incoming.dart index 1fa9cd7e..4782e330 100644 --- a/lib/flutter_callkit_incoming.dart +++ b/lib/flutter_callkit_incoming.dart @@ -133,6 +133,12 @@ class FlutterCallkitIncoming { return await _channel.invokeMethod("requestNotificationPermission", data); } + /// Request permisstion show notification for Android(14)+ + /// Only Android: show request permission for ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT + static Future requestFullIntentPermission() async { + return await _channel.invokeMethod("requestFullIntentPermission"); + } + static CallEvent? _receiveCallEvent(dynamic data) { Event? event; Map body = {}; diff --git a/pubspec.yaml b/pubspec.yaml index 93f63dd7..1144c8ee 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.4+1 +version: 2.0.4+2 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