android端自定义通话通知
收到通知后实现目标:
- 显示接听和拒接
- 锁屏时,则亮起屏幕
- 响铃
- 震动
功能分析:
- 收到通知后将Intent传递到拉起自定义通知的处理方法(
startCustomNotify
) - 定义通知管类(
CallkitNotificationManager
),用来管理通知通道的创建和销毁。 - 如果需要自定义全屏,则需要自定义activity
- 创建广播监听类(
CallkitIncomingBroadcastReceiver
),用来处理接听或挂断
一、拉起通知的方法(startCustomNotify)
Application.java
public void startCustomNotify(Intent intent) {CallkitNotificationManager manager = new CallkitNotificationManager(this);String title = (String) Objects.requireNonNull(Objects.requireNonNull(intent.getExtras()).get("gcm.notification.body"));manager.showIncomingNotification(title);}
二、自定义通知的管理类
CallkitNotificationManager.kt
import android.annotation.SuppressLint
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.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person
import com.ilifesmart.mslict.MslIctApplicationclass CallkitNotificationManager(private val context: Context) {companion object {const val NOTIFICATION_CHANNEL_ID_INCOMING = "callkit_incoming_channel_id"}private var notificationBuilder: NotificationCompat.Builder? = nullprivate fun getNotificationManager(): NotificationManagerCompat {return NotificationManagerCompat.from(context)}// 唤醒屏幕private fun wakeLockRequest() {val pm = context.getSystemService(Activity.POWER_SERVICE) as PowerManagerval wakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.FULL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,"Callkit:PowerManager")wakeLock.acquire(3000L)}@SuppressLint("MissingPermission")fun showIncomingNotification(title: String) {wakeLockRequest()Handler(Looper.getMainLooper()).postDelayed({playVibrator()val callkitNotification = getIncomingNotification(title)callkitNotification?.let {getNotificationManager().notify(it.id, callkitNotification.notification)}}, 500)}fun clearIncomingNotification(isAccepted: Boolean) {vibrator?.cancel()vibrator = nullcontext.sendBroadcast(CallkitIncomingActivity.getIntentEnded(context, isAccepted))val notificationId ="callkit_incoming".hashCode()getNotificationManager().cancel(notificationId)if (isAccepted) {MslIctApplication.getApplication().startActivityInNotify()}}// 获取Notification@SuppressLint("MissingPermission")fun getIncomingNotification(title: String): CallkitNotification? {val notificationId ="callkit_incoming".hashCode()createNotificationChanel()notificationBuilder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID_INCOMING)val caller = Person.Builder().setName(title).setImportant(true).build()// 设置图标,这个必须要写,值可以自定义notificationBuilder?.setSmallIcon(context.applicationInfo.icon)// 优先级,最好写这个等级notificationBuilder?.priority = NotificationCompat.PRIORITY_MAX// 设置全屏的通知,当使用NotificationCompat.CallStyle时必须绑定前台服务/fullScreenIntent/user-initiated jobnotificationBuilder?.setFullScreenIntent(getActivityPendingIntent(notificationId), true)// 自动超时时间notificationBuilder?.setTimeoutAfter(20 * 1000L)// 点击通知后自动移除通知notificationBuilder?.setAutoCancel(false)// 持续进行的通知,除非卸载APP或手动清除通知
// notificationBuilder?.setOngoing(true)// 仅限第一次显示时会有声音/震动notificationBuilder?.setOnlyAlertOnce(true)// 设置呼吸灯notificationBuilder?.setLights(Color.RED, 1000, 1000)// 设置通知的发布时间notificationBuilder?.setWhen(System.currentTimeMillis())if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {notificationBuilder?.setStyle(NotificationCompat.CallStyle.forIncomingCall(caller,createDeclineIntent(context),createAnswerIntent(context),))} else {notificationBuilder?.setContentTitle(title)val declineAction: NotificationCompat.Action = NotificationCompat.Action.Builder(R.drawable.ic_decline,context.getString(R.string.text_decline),createDeclineIntent(context)).build()notificationBuilder?.addAction(declineAction)val acceptAction: NotificationCompat.Action = NotificationCompat.Action.Builder(R.drawable.ic_accept,context.getString(R.string.text_accept),createAnswerIntent(context)).build()notificationBuilder?.addAction(acceptAction)}val notification = notificationBuilder?.build()return notification?.let { CallkitNotification(notificationId, it) }}// 拒绝时的处理事件,实际绑定到CallkitIncomingBroadcastReceiver的广播监听private fun createDeclineIntent(context: Context): PendingIntent {val intent = Intent(context, CallkitIncomingBroadcastReceiver::class.java).setAction(CallkitConstants.ACTION_CALL_DECLINE)return PendingIntent.getBroadcast(context, 0, intent, getFlagPendingIntent())}// 接听时的处理事件,实际绑定到CallkitIncomingBroadcastReceiver的广播监听private fun createAnswerIntent(context: Context): PendingIntent {val intent = Intent(context, CallkitIncomingBroadcastReceiver::class.java).setAction(CallkitConstants.ACTION_CALL_ACCEPT)return PendingIntent.getBroadcast(context, 1, intent, getFlagPendingIntent())}// CallkitIncomingActivity全屏显示的页面private fun getActivityPendingIntent(id: Int): PendingIntent {val intent = CallkitIncomingActivity.getIntent(context)return PendingIntent.getActivity(context, id, intent, getFlagPendingIntent())}private fun getFlagPendingIntent(): Int {val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE} else {PendingIntent.FLAG_UPDATE_CURRENT}return flags}private var vibrator: Vibrator? = null// 播放震动private fun playVibrator() {vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {val vibratorManager =context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManagervibratorManager.defaultVibrator} else {context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {vibrator?.vibrate(VibrationEffect.createWaveform(longArrayOf(0L, 200L, 100L, 300L),0))} else {vibrator?.vibrate(longArrayOf(0L, 200L, 100L, 300L), 0)}}// 创建通知通道@SuppressLint("DiscouragedApi")private fun createNotificationChanel() {val resId = context.resources.getIdentifier("ringtone_default", "raw", context.packageName)if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {getNotificationManager().apply {var channelCall = getNotificationChannel(NOTIFICATION_CHANNEL_ID_INCOMING)if (channelCall == null) {channelCall = NotificationChannel(NOTIFICATION_CHANNEL_ID_INCOMING,"Incoming Call",NotificationManager.IMPORTANCE_HIGH).apply {description = "Door Phone Call Notification"lightColor = Color.REDenableLights(true)
// enableVibration(true)}}channelCall.lockscreenVisibility = Notification.VISIBILITY_PUBLICchannelCall.importance = NotificationManager.IMPORTANCE_HIGHchannelCall.setSound(Uri.parse("android.resource://" + context.packageName + "/$resId"), Notification.AUDIO_ATTRIBUTES_DEFAULT)createNotificationChannel(channelCall)}}}fun destroy() {vibrator?.cancel()vibrator = null}
}data class CallkitNotification(val id: Int, val notification: Notification)
三、自定义通知的管理类(CallkitIncomingActivity
)
我是参考的这里的
flutter_callkit_incoming/CallkitIncomingActivity.kt
四、广播监听类
CallkitIncomingBroadcastReceiver.kt
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import com.ilifesmart.mslict.notibar.NotificationMgrclass CallkitIncomingBroadcastReceiver : BroadcastReceiver() {companion object {private const val TAG = "CallkitIncomingReceiver"fun getIntentDecline(context: Context, data: Bundle?) =Intent(context, CallkitIncomingBroadcastReceiver::class.java).apply {action = "${context.packageName}.ACTION_CALL_DECLINE"putExtra("EXTRA_CALLKIT_INCOMING_DATA", data)}}@SuppressLint("MissingPermission")override fun onReceive(context: Context, intent: Intent) {val action = intent.action ?: returnval callkitNotificationManager = CallkitNotificationManager(context)when (action) {CallkitConstants.ACTION_CALL_DECLINE -> {try {callkitNotificationManager.clearIncomingNotification(false)// 处理拒绝逻辑} catch (error: Exception) {Log.e(TAG, null, error)}}CallkitConstants.ACTION_CALL_ACCEPT -> {try {NotificationMgr.getInstance().acceptCallNotify()// 处理接听逻辑} catch (error: Exception) {Log.e(TAG, null, error)}}}}
}
五、常量
CallkitConstants.kt
object CallkitConstants {const val ACTION_CALL_ACCEPT ="com.example.app.ACTION_CALL_ACCEPT"const val ACTION_CALL_DECLINE ="com.example.app.ACTION_CALL_DECLINE"
}
六、注意
// 允许在锁屏时显示界面可以在onCreate中添加如下
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);