From 05922b6b1f0243ffb20504bc1faf252ae56e094e Mon Sep 17 00:00:00 2001 From: Hien Nguyen Date: Wed, 26 Jul 2023 03:59:08 +0700 Subject: [PATCH 1/2] Update consumer-rules.pro --- android/consumer-rules.pro | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/consumer-rules.pro b/android/consumer-rules.pro index 9eec6cb7..8cdcd047 100644 --- a/android/consumer-rules.pro +++ b/android/consumer-rules.pro @@ -1,3 +1,5 @@ #flutter_callkit_incoming # Issue: https://github.com/hiennguyen92/flutter_callkit_incoming/issues/171 --keep class com.hiennv.flutter_callkit_incoming.** { *; } \ No newline at end of file +-keep class com.hiennv.flutter_callkit_incoming.** { *; } +-keep class com.fasterxml.** { *; } +-keep class org.codehaus.** { *; } From bb4c91a513ccfa7df9d2c3b19b4c92ffbb54d7c5 Mon Sep 17 00:00:00 2001 From: Hien Nguyen Date: Mon, 25 Sep 2023 16:34:07 +0700 Subject: [PATCH 2/2] 2.0.0+1: * Fixed some bugs. * Support request permission for Android 13+ `requestNotificationPermission` --- CHANGELOG.md | 5 + README.md | 10 + android/build.gradle | 2 +- android/src/main/AndroidManifest.xml | 1 + .../CallkitNotificationManager.kt | 291 +++++++++++------- .../FlutterCallkitIncomingPlugin.kt | 127 +++++--- android/src/main/res/values/strings.xml | 1 + android/src/main/res/values/styles.xml | 5 + example/android/app/build.gradle | 2 +- example/android/build.gradle | 2 +- example/ios/Podfile.lock | 114 +++++++ example/lib/home_page.dart | 8 + example/pubspec.lock | 6 +- lib/flutter_callkit_incoming.dart | 9 +- pubspec.yaml | 2 +- 15 files changed, 421 insertions(+), 164 deletions(-) create mode 100644 example/ios/Podfile.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index a795f9c9..5d8fad4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0+2 + +* Fixed some bugs. +* Support request permission for Android 13+ `requestNotificationPermission` + ## 2.0.0+1 * Fixed some bugs. diff --git a/README.md b/README.md index e3d25164..07fab3b0 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,16 @@ A Flutter plugin to show incoming call in your Flutter app(Custom for Android/Ca ``` Note: Firebase Message: `@pragma('vm:entry-point')`
https://github.com/firebase/flutterfire/blob/master/docs/cloud-messaging/receive.md#apple-platforms-and-android + + * request permission for post Notification Android 13+ + For Android 13 and above, please `requestNotificationPermission` before `showCallkitIncoming` + ```dart + await FlutterCallkitIncoming.requestNotificationPermission({ + "rationaleMessagePermission": "Notification permission is required, to show notification.", + "postNotificationMessageRequired": "Notification permission is required, Please allow notification permission from setting." + }); + ``` + * Show miss call notification ```dart this._currentUuid = _uuid.v4(); diff --git a/android/build.gradle b/android/build.gradle index d29e2c36..5cabe22e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,7 +28,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 31 + compileSdkVersion 33 sourceSets { main.java.srcDirs += 'src/main/kotlin' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index a372fcf7..dabdfcb3 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + 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 9be172cc..faebc84b 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 @@ -1,11 +1,15 @@ package com.hiennv.flutter_callkit_incoming +import android.Manifest +import android.app.Activity import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context +import android.content.DialogInterface import android.content.Intent +import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.Color import android.graphics.drawable.Drawable @@ -15,9 +19,12 @@ import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper +import android.provider.Settings import android.text.TextUtils 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 com.hiennv.flutter_callkit_incoming.widgets.CircleTransform @@ -30,6 +37,7 @@ import okhttp3.OkHttpClient class CallkitNotificationManager(private val context: Context) { companion object { + const val PERMISSION_NOTIFICATION_REQUEST_CODE = 6969 const val EXTRA_TIME_START_CALL = "EXTRA_TIME_START_CALL" @@ -41,6 +49,7 @@ class CallkitNotificationManager(private val context: Context) { private var notificationViews: RemoteViews? = null private var notificationSmallViews: RemoteViews? = null private var notificationId: Int = 9696 + private var dataNotificationPermission: Map = HashMap() private var targetLoadAvatarDefault = object : Target { override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) { @@ -77,14 +86,14 @@ class CallkitNotificationManager(private val context: Context) { notificationId = data.getString(CallkitConstants.EXTRA_CALLKIT_ID, "callkit_incoming").hashCode() createNotificationChanel( - data.getString( - CallkitConstants.EXTRA_CALLKIT_INCOMING_CALL_NOTIFICATION_CHANNEL_NAME, - "Incoming Call" - ), - data.getString( - CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_NOTIFICATION_CHANNEL_NAME, - "Missed Call" - ), + data.getString( + CallkitConstants.EXTRA_CALLKIT_INCOMING_CALL_NOTIFICATION_CHANNEL_NAME, + "Incoming Call" + ), + data.getString( + CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_NOTIFICATION_CHANNEL_NAME, + "Missed Call" + ), ) notificationBuilder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_INCOMING) @@ -102,7 +111,7 @@ class CallkitNotificationManager(private val context: Context) { notificationBuilder.setOnlyAlertOnce(true) notificationBuilder.setSound(null) notificationBuilder.setFullScreenIntent( - getActivityPendingIntent(notificationId, data), true + getActivityPendingIntent(notificationId, data), true ) notificationBuilder.setContentIntent(getActivityPendingIntent(notificationId, data)) notificationBuilder.setDeleteIntent(getTimeOutPendingIntent(notificationId, data)) @@ -124,25 +133,25 @@ class CallkitNotificationManager(private val context: Context) { notificationBuilder.setChannelId(NOTIFICATION_CHANNEL_ID_INCOMING) notificationBuilder.priority = NotificationCompat.PRIORITY_MAX val isCustomNotification = - data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_NOTIFICATION, false) + data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_NOTIFICATION, false) val isCustomSmallExNotification = - data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_SMALL_EX_NOTIFICATION, false) + data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_SMALL_EX_NOTIFICATION, false) if (isCustomNotification) { notificationViews = - RemoteViews(context.packageName, R.layout.layout_custom_notification) + RemoteViews(context.packageName, R.layout.layout_custom_notification) initNotificationViews(notificationViews!!, data) if ((Build.MANUFACTURER.equals( - "Samsung", - ignoreCase = true - ) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) || isCustomSmallExNotification + "Samsung", + ignoreCase = true + ) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) || isCustomSmallExNotification ) { notificationSmallViews = - RemoteViews(context.packageName, R.layout.layout_custom_small_ex_notification) + RemoteViews(context.packageName, R.layout.layout_custom_small_ex_notification) initNotificationViews(notificationSmallViews!!, data) } else { notificationSmallViews = - RemoteViews(context.packageName, R.layout.layout_custom_small_notification) + RemoteViews(context.packageName, R.layout.layout_custom_small_notification) initNotificationViews(notificationSmallViews!!, data) } @@ -154,29 +163,29 @@ class CallkitNotificationManager(private val context: Context) { val avatarUrl = data.getString(CallkitConstants.EXTRA_CALLKIT_AVATAR, "") if (avatarUrl != null && avatarUrl.isNotEmpty()) { val headers = - data.getSerializable(CallkitConstants.EXTRA_CALLKIT_HEADERS) as HashMap + data.getSerializable(CallkitConstants.EXTRA_CALLKIT_HEADERS) as HashMap getPicassoInstance(context, headers).load(avatarUrl) - .into(targetLoadAvatarDefault) + .into(targetLoadAvatarDefault) } notificationBuilder.setContentTitle( - data.getString( - CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, - "" - ) + data.getString( + CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, + "" + ) ) notificationBuilder.setContentText(data.getString(CallkitConstants.EXTRA_CALLKIT_HANDLE, "")) 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) + 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) + R.drawable.ic_accept, + if (TextUtils.isEmpty(textDecline)) context.getString(R.string.text_accept) else textAccept, + getAcceptPendingIntent(notificationId, data) ).build() notificationBuilder.addAction(acceptAction) } @@ -187,55 +196,55 @@ class CallkitNotificationManager(private val context: Context) { private fun initNotificationViews(remoteViews: RemoteViews, data: Bundle) { remoteViews.setTextViewText( - R.id.tvNameCaller, - data.getString(CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, "") + R.id.tvNameCaller, + data.getString(CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, "") ) remoteViews.setTextViewText( - R.id.tvNumber, - data.getString(CallkitConstants.EXTRA_CALLKIT_HANDLE, "") + R.id.tvNumber, + data.getString(CallkitConstants.EXTRA_CALLKIT_HANDLE, "") ) remoteViews.setOnClickPendingIntent( - R.id.llDecline, - getDeclinePendingIntent(notificationId, data) + R.id.llDecline, + getDeclinePendingIntent(notificationId, data) ) val textDecline = data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_DECLINE, "") remoteViews.setTextViewText( - R.id.tvDecline, - if (TextUtils.isEmpty(textDecline)) context.getString(R.string.text_decline) else textDecline + R.id.tvDecline, + if (TextUtils.isEmpty(textDecline)) context.getString(R.string.text_decline) else textDecline ) remoteViews.setOnClickPendingIntent( - R.id.llAccept, - getAcceptPendingIntent(notificationId, data) + R.id.llAccept, + getAcceptPendingIntent(notificationId, data) ) val textAccept = data.getString(CallkitConstants.EXTRA_CALLKIT_TEXT_ACCEPT, "") remoteViews.setTextViewText( - R.id.tvAccept, - if (TextUtils.isEmpty(textAccept)) context.getString(R.string.text_accept) else textAccept + R.id.tvAccept, + if (TextUtils.isEmpty(textAccept)) context.getString(R.string.text_accept) else textAccept ) val avatarUrl = data.getString(CallkitConstants.EXTRA_CALLKIT_AVATAR, "") if (avatarUrl != null && avatarUrl.isNotEmpty()) { val headers = - data.getSerializable(CallkitConstants.EXTRA_CALLKIT_HEADERS) as HashMap + data.getSerializable(CallkitConstants.EXTRA_CALLKIT_HEADERS) as HashMap getPicassoInstance(context, headers).load(avatarUrl) - .transform(CircleTransform()) - .into(targetLoadAvatarCustomize) + .transform(CircleTransform()) + .into(targetLoadAvatarCustomize) } } fun showMissCallNotification(data: Bundle) { notificationId = data.getInt( - CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_ID, - data.getString(CallkitConstants.EXTRA_CALLKIT_ID, "callkit_incoming").hashCode() + 1 + CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_ID, + data.getString(CallkitConstants.EXTRA_CALLKIT_ID, "callkit_incoming").hashCode() + 1 ) createNotificationChanel( - data.getString( - CallkitConstants.EXTRA_CALLKIT_INCOMING_CALL_NOTIFICATION_CHANNEL_NAME, - "Incoming Call" - ), - data.getString( - CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_NOTIFICATION_CHANNEL_NAME, - "Missed Call" - ), + data.getString( + CallkitConstants.EXTRA_CALLKIT_INCOMING_CALL_NOTIFICATION_CHANNEL_NAME, + "Incoming Call" + ), + data.getString( + CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_NOTIFICATION_CHANNEL_NAME, + "Missed Call" + ), ) val missedCallSound: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) val typeCall = data.getInt(CallkitConstants.EXTRA_CALLKIT_TYPE, -1) @@ -258,78 +267,78 @@ class CallkitNotificationManager(private val context: Context) { notificationBuilder.setSubText(if (TextUtils.isEmpty(textMissedCall)) context.getString(R.string.text_missed_call) else textMissedCall) notificationBuilder.setSmallIcon(smallIcon) val isCustomNotification = - data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_NOTIFICATION, false) + data.getBoolean(CallkitConstants.EXTRA_CALLKIT_IS_CUSTOM_NOTIFICATION, false) val count = data.getInt(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_COUNT, 1) if (count > 1) { notificationBuilder.setNumber(count) } if (isCustomNotification) { notificationViews = - RemoteViews(context.packageName, R.layout.layout_custom_miss_notification) + RemoteViews(context.packageName, R.layout.layout_custom_miss_notification) notificationViews?.setTextViewText( - R.id.tvNameCaller, - data.getString(CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, "") + R.id.tvNameCaller, + data.getString(CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, "") ) notificationViews?.setTextViewText( - R.id.tvNumber, - data.getString(CallkitConstants.EXTRA_CALLKIT_HANDLE, "") + R.id.tvNumber, + data.getString(CallkitConstants.EXTRA_CALLKIT_HANDLE, "") ) notificationViews?.setOnClickPendingIntent( - R.id.llCallback, - getCallbackPendingIntent(notificationId, data) + R.id.llCallback, + getCallbackPendingIntent(notificationId, data) ) val isShowCallback = data.getBoolean( - CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_SHOW, - true + CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_SHOW, + true ) notificationViews?.setViewVisibility( - R.id.llCallback, - if (isShowCallback) View.VISIBLE else View.GONE + R.id.llCallback, + if (isShowCallback) View.VISIBLE else View.GONE ) val textCallback = data.getString(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_TEXT, "") notificationViews?.setTextViewText( - R.id.tvCallback, - if (TextUtils.isEmpty(textCallback)) context.getString(R.string.text_call_back) else textCallback + R.id.tvCallback, + if (TextUtils.isEmpty(textCallback)) context.getString(R.string.text_call_back) else textCallback ) val avatarUrl = data.getString(CallkitConstants.EXTRA_CALLKIT_AVATAR, "") if (avatarUrl != null && avatarUrl.isNotEmpty()) { val headers = - data.getSerializable(CallkitConstants.EXTRA_CALLKIT_HEADERS) as HashMap + data.getSerializable(CallkitConstants.EXTRA_CALLKIT_HEADERS) as HashMap getPicassoInstance(context, headers).load(avatarUrl) - .transform(CircleTransform()).into(targetLoadAvatarCustomize) + .transform(CircleTransform()).into(targetLoadAvatarCustomize) } notificationBuilder.setStyle(NotificationCompat.DecoratedCustomViewStyle()) notificationBuilder.setCustomContentView(notificationViews) notificationBuilder.setCustomBigContentView(notificationViews) } else { notificationBuilder.setContentTitle( - data.getString( - CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, - "" - ) + data.getString( + CallkitConstants.EXTRA_CALLKIT_NAME_CALLER, + "" + ) ) notificationBuilder.setContentText(data.getString(CallkitConstants.EXTRA_CALLKIT_HANDLE, "")) val avatarUrl = data.getString(CallkitConstants.EXTRA_CALLKIT_AVATAR, "") if (avatarUrl != null && avatarUrl.isNotEmpty()) { val headers = - data.getSerializable(CallkitConstants.EXTRA_CALLKIT_HEADERS) as HashMap + data.getSerializable(CallkitConstants.EXTRA_CALLKIT_HEADERS) as HashMap getPicassoInstance(context, headers).load(avatarUrl) - .into(targetLoadAvatarDefault) + .into(targetLoadAvatarDefault) } val isShowCallback = data.getBoolean( - CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_SHOW, - true + CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_SHOW, + true ) if (isShowCallback) { val textCallback = - data.getString(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_TEXT, "") + data.getString(CallkitConstants.EXTRA_CALLKIT_MISSED_CALL_CALLBACK_TEXT, "") val callbackAction: NotificationCompat.Action = NotificationCompat.Action.Builder( - R.drawable.ic_accept, - if (TextUtils.isEmpty(textCallback)) context.getString(R.string.text_call_back) else textCallback, - getCallbackPendingIntent(notificationId, data) + R.drawable.ic_accept, + if (TextUtils.isEmpty(textCallback)) context.getString(R.string.text_call_back) else textCallback, + getCallbackPendingIntent(notificationId, data) ).build() notificationBuilder.addAction(callbackAction) } @@ -385,8 +394,8 @@ class CallkitNotificationManager(private val context: Context) { } private fun createNotificationChanel( - incomingCallChannelName: String, - missedCallChannelName: String, + incomingCallChannelName: String, + missedCallChannelName: String, ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { getNotificationManager().apply { @@ -395,13 +404,13 @@ class CallkitNotificationManager(private val context: Context) { channelCall.setSound(null, null) } else { channelCall = NotificationChannel( - NOTIFICATION_CHANNEL_ID_INCOMING, - incomingCallChannelName, - NotificationManager.IMPORTANCE_HIGH + NOTIFICATION_CHANNEL_ID_INCOMING, + incomingCallChannelName, + NotificationManager.IMPORTANCE_HIGH ).apply { description = "" vibrationPattern = - longArrayOf(0, 1000, 500, 1000, 500) + longArrayOf(0, 1000, 500, 1000, 500) lightColor = Color.RED enableLights(true) enableVibration(true) @@ -415,9 +424,9 @@ class CallkitNotificationManager(private val context: Context) { createNotificationChannel(channelCall) val channelMissedCall = NotificationChannel( - NOTIFICATION_CHANNEL_ID_MISSED, - missedCallChannelName, - NotificationManager.IMPORTANCE_DEFAULT + NOTIFICATION_CHANNEL_ID_MISSED, + missedCallChannelName, + NotificationManager.IMPORTANCE_DEFAULT ).apply { description = "" vibrationPattern = longArrayOf(0, 1000) @@ -433,9 +442,9 @@ class CallkitNotificationManager(private val context: Context) { private fun getAcceptPendingIntent(id: Int, data: Bundle): PendingIntent { val intentTransparent = TransparentActivity.getIntent( - context, - CallkitConstants.ACTION_CALL_ACCEPT, - data + context, + CallkitConstants.ACTION_CALL_ACCEPT, + data ) return PendingIntent.getActivity(context, id, intentTransparent, getFlagPendingIntent()) } @@ -452,9 +461,9 @@ class CallkitNotificationManager(private val context: Context) { private fun getCallbackPendingIntent(id: Int, data: Bundle): PendingIntent { val intentTransparent = TransparentActivity.getIntent( - context, - CallkitConstants.ACTION_CALL_CALLBACK, - data + context, + CallkitConstants.ACTION_CALL_CALLBACK, + data ) return PendingIntent.getActivity(context, id, intentTransparent, getFlagPendingIntent()) } @@ -484,18 +493,88 @@ class CallkitNotificationManager(private val context: Context) { private fun getPicassoInstance(context: Context, headers: HashMap): Picasso { val client = OkHttpClient.Builder() - .addNetworkInterceptor { chain -> - val newRequestBuilder: okhttp3.Request.Builder = chain.request().newBuilder() - for ((key, value) in headers) { - newRequestBuilder.addHeader(key, value.toString()) + .addNetworkInterceptor { chain -> + val newRequestBuilder: okhttp3.Request.Builder = chain.request().newBuilder() + for ((key, value) in headers) { + newRequestBuilder.addHeader(key, value.toString()) + } + chain.proceed(newRequestBuilder.build()) } - chain.proceed(newRequestBuilder.build()) - } - .build() + .build() return Picasso.Builder(context) - .downloader(OkHttp3Downloader(client)) - .build() + .downloader(OkHttp3Downloader(client)) + .build() + } + + + fun requestNotificationPermission(activity: Activity?, map: Map) { + this.dataNotificationPermission = map + if (Build.VERSION.SDK_INT > 32) { + activity?.let { + ActivityCompat.requestPermissions(it, + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + PERMISSION_NOTIFICATION_REQUEST_CODE) + } + } + } + + fun onRequestPermissionsResult(activity: Activity?, requestCode: Int, grantResults: IntArray) { + when (requestCode) { + PERMISSION_NOTIFICATION_REQUEST_CODE -> { + if (grantResults.isNotEmpty() && + grantResults[0] === PackageManager.PERMISSION_GRANTED) { + // allow + } else { + //deny + activity?.let { + if (ActivityCompat.shouldShowRequestPermissionRationale(it, Manifest.permission.POST_NOTIFICATIONS)) { + //showDialogPermissionRationale() + if (this.dataNotificationPermission["rationaleMessagePermission"] != null) { + showDialogMessage(it, this.dataNotificationPermission["rationaleMessagePermission"] as String) { dialog, _ -> + dialog?.dismiss() + requestNotificationPermission(activity, this.dataNotificationPermission) + } + } else { + requestNotificationPermission(activity, this.dataNotificationPermission) + } + } else { + //Open Setting + if (this.dataNotificationPermission["postNotificationMessageRequired"] != null) { + showDialogMessage(it, this.dataNotificationPermission["postNotificationMessageRequired"] as String) { dialog, _ -> + dialog?.dismiss() + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", it.packageName, null)) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + it.startActivity(intent) + } + } else { + showDialogMessage(it, it.resources.getString(R.string.text_post_notification_message_required)) { dialog, _ -> + dialog?.dismiss() + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", it.packageName, null)) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + it.startActivity(intent) + } + } + } + } + } + } + } + } + + private fun showDialogMessage(activity: Activity?, message: String, okListener: DialogInterface.OnClickListener) { + activity?.let { + AlertDialog.Builder(it, R.style.DialogTheme) + .setMessage(message) + .setPositiveButton(android.R.string.ok, okListener) + .setNegativeButton(android.R.string.cancel, null) + .create() + .show() + } } } + + 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 9947aad7..f3db194c 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,28 +1,32 @@ 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 androidx.annotation.NonNull -import androidx.annotation.Nullable - +import androidx.appcompat.app.AlertDialog +import androidx.core.app.ActivityCompat import com.hiennv.flutter_callkit_incoming.Utils.Companion.reapCollection - import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.plugin.common.BinaryMessenger -import io.flutter.plugin.common.EventChannel -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.* import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result import java.lang.ref.WeakReference + /** FlutterCallkitIncomingPlugin */ -class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { +class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.RequestPermissionsResultListener { companion object { const val EXTRA_CALLKIT_CALL_DATA = "EXTRA_CALLKIT_CALL_DATA" @@ -76,6 +80,7 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA eventHandlers.add(WeakReference(handler)) events.setStreamHandler(handler) } + } /// The MethodChannel that will the communication between Flutter and native Android @@ -95,10 +100,10 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA callkitNotificationManager?.showIncomingNotification(data.toBundle()) //send BroadcastReceiver context?.sendBroadcast( - CallkitIncomingBroadcastReceiver.getIntentIncoming( - requireNotNull(context), - data.toBundle() - ) + CallkitIncomingBroadcastReceiver.getIntentIncoming( + requireNotNull(context), + data.toBundle() + ) ) } @@ -108,19 +113,19 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA public fun startCall(data: Data) { context?.sendBroadcast( - CallkitIncomingBroadcastReceiver.getIntentStart( - requireNotNull(context), - data.toBundle() - ) + CallkitIncomingBroadcastReceiver.getIntentStart( + requireNotNull(context), + data.toBundle() + ) ) } public fun endCall(data: Data) { context?.sendBroadcast( - CallkitIncomingBroadcastReceiver.getIntentEnded( - requireNotNull(context), - data.toBundle() - ) + CallkitIncomingBroadcastReceiver.getIntentEnded( + requireNotNull(context), + data.toBundle() + ) ) } @@ -128,10 +133,10 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA val calls = getDataActiveCalls(context) calls.forEach { context?.sendBroadcast( - CallkitIncomingBroadcastReceiver.getIntentEnded( - requireNotNull(context), - it.toBundle() - ) + CallkitIncomingBroadcastReceiver.getIntentEnded( + requireNotNull(context), + it.toBundle() + ) ) } removeAllCalls(context) @@ -151,10 +156,10 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA data.from = "notification" //send BroadcastReceiver context?.sendBroadcast( - CallkitIncomingBroadcastReceiver.getIntentIncoming( - requireNotNull(context), - data.toBundle() - ) + CallkitIncomingBroadcastReceiver.getIntentIncoming( + requireNotNull(context), + data.toBundle() + ) ) result.success("OK") } @@ -167,10 +172,10 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA "startCall" -> { val data = Data(call.arguments() ?: HashMap()) context?.sendBroadcast( - CallkitIncomingBroadcastReceiver.getIntentStart( - requireNotNull(context), - data.toBundle() - ) + CallkitIncomingBroadcastReceiver.getIntentStart( + requireNotNull(context), + data.toBundle() + ) ) result.success("OK") } @@ -194,13 +199,16 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA sendEvent(CallkitConstants.ACTION_CALL_TOGGLE_HOLD, map); result.success("OK") } + "isMuted" -> { + result.success(false) + } "endCall" -> { val data = Data(call.arguments() ?: HashMap()) context?.sendBroadcast( - CallkitIncomingBroadcastReceiver.getIntentEnded( - requireNotNull(context), - data.toBundle() - ) + CallkitIncomingBroadcastReceiver.getIntentEnded( + requireNotNull(context), + data.toBundle() + ) ) result.success("OK") } @@ -212,17 +220,17 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA calls.forEach { if (it.isAccepted) { context?.sendBroadcast( - CallkitIncomingBroadcastReceiver.getIntentEnded( - requireNotNull(context), - it.toBundle() - ) + CallkitIncomingBroadcastReceiver.getIntentEnded( + requireNotNull(context), + it.toBundle() + ) ) } else { context?.sendBroadcast( - CallkitIncomingBroadcastReceiver.getIntentDecline( - requireNotNull(context), - it.toBundle() - ) + CallkitIncomingBroadcastReceiver.getIntentDecline( + requireNotNull(context), + it.toBundle() + ) ) } } @@ -235,6 +243,15 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA "getDevicePushTokenVoIP" -> { result.success("") } + "requestNotificationPermission" -> { + val map = buildMap { + val args = call.arguments + if (args is Map<*, *>) { + putAll(args as Map) + } + } + callkitNotificationManager?.requestNotificationPermission(activity, map) + } } } catch (error: Exception) { result.error("error", error.message, "") @@ -247,16 +264,18 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA } override fun onAttachedToActivity(binding: ActivityPluginBinding) { - this.activity = binding.activity - this.context = binding.activity.applicationContext + instance.context = binding.activity.applicationContext + instance.activity = binding.activity + binding.addRequestPermissionsResultListener(this) } override fun onDetachedFromActivityForConfigChanges() { } override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - this.activity = binding.activity - this.context = binding.activity.applicationContext + instance.context = binding.activity.applicationContext + instance.activity = binding.activity + binding.addRequestPermissionsResultListener(this) } override fun onDetachedFromActivity() {} @@ -271,8 +290,8 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA fun send(event: String, body: Map) { val data = mapOf( - "event" to event, - "body" to body + "event" to event, + "body" to body ) Handler(Looper.getMainLooper()).post { eventSink?.success(data) @@ -283,4 +302,12 @@ class FlutterCallkitIncomingPlugin : FlutterPlugin, MethodCallHandler, ActivityA eventSink = null } } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray): Boolean { + instance.callkitNotificationManager?.onRequestPermissionsResult(instance.activity, requestCode, grantResults) + return true + } + + + } diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index 8379abf6..955d9785 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -5,4 +5,5 @@ Decline Missed call Call back + Notification permission is required, Please allow notification permission from setting. \ No newline at end of file diff --git a/android/src/main/res/values/styles.xml b/android/src/main/res/values/styles.xml index 732eb885..4dc06acd 100644 --- a/android/src/main/res/values/styles.xml +++ b/android/src/main/res/values/styles.xml @@ -9,6 +9,11 @@ + + +