Android 自定义来电秀实现总结
目录
- 前言
- 实现思想
- 申请权限
- 静态权限
- 动态权限
- 监听电话
- BroadcastReceiver +悬浮窗显示实现
- InCallService + Activity实现
前言
该文章为对工作中部分业务实现的总结,阅读时间:20分钟,版本:Android 6.0 - 9.0 update time 2021年02月03日11:48:55 文章可能存在不足之处,还望评论批评,一起学习进步。
要想实现自定义 来电秀,首先我们先这样 再这样,然后你这样,最后你再这样一下,就可以了,很好实现的,听懂了么?-,-
效果图
- 添加包活lib,提高App在设置成功后 退居后台,成功拉起的概率
- 项目中已经包含lib_ijk的代码,我们可以添加视频来电展示,添加美女或者豪车等全屏视频,效果更佳。
- 由于反编译能力有限,对于多种机型权限的跳转(后续可以开起 无障碍服务,直接一步搞定多种需要用户手动设置操作)
- 该Demo中有一部分不完善的Rom 权限跳转机制,后续还需要时间来完善。
参考文章 来电秀实现
实现思想
- 通过监听手机Service 分辨来电状态,然后弹出我们自定义的来电页面,覆盖系统来电页面。
- 通过相关API (主要两种:读取来电系统的Notification信息 和 模拟耳机线控的方式进行挂断/接听)实现接听和挂断功能。我这里会使用两种(低版本 使用电话状态广播监听,高版本使用InCallService) 监听电话状态的Service 及两种界面展示 来呈现来电信息,多个界面和多个Service的监听 能够增加高版本的容错率兼容性。
- 实现自定义的拨号界面 或者 直接使用系统的拨号界面。
注意:因为篇幅问题,博客只会截取部分代码,太长读者很难读下去,Demo已经调试通过,如果有想看源码的可以移步到我的 GitHub项目地址
申请权限
静态权限
电话应用,会用到很多权限,我这里尽可能多的静态注册了一些权限,如果引入项目中,需要甄别下,代码如下:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <!-- 读取联系人权限 --> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" /> <!-- 读写 联系信息 显示联系人名称 --> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.WRITE_CALL_LOG" /> <uses-permission android:name="android.permission.READ_LOGS" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.GET_TASKS" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.BROADCAST_PACKAGE_ADDED" /> <uses-permission android:name="android.permission.BROADCAST_PACKAGE_CHANGED" /> <uses-permission android:name="android.permission.BROADCAST_PACKAGE_INSTALL" /> <uses-permission android:name="android.permission.BROADCAST_PACKAGE_REPLACED" /> <uses-permission android:name="android.permission.RESTART_PACKAGES" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <!--android 9.0上使用前台服务,需要添加权限--> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
动态权限
AndPermission.with(this) .runtime() .permission( Permission.Group.PHONE, Permission.Group.LOCATION, Permission.Group.CALL_LOG ) .onGranted { Toast.makeText(applicationContext, "权限同意", Toast.LENGTH_SHORT).show() }.onDenied { Toast.makeText(applicationContext, "权限拒绝", Toast.LENGTH_SHORT).show() }.start()
上述代码,为自己测试使用的Demo,所以请求权限直接请求分组中的全部权限了,项目中根据需要动态申请部分权限
虽然我们已经申请了这么多权限,但是为了能够替换系统电话界面成功,还有一部分权限是需要通过弹框来引导用户去 设置中开启的。
# CallerShowPermissionManager.kt /** * 判断是否有 锁屏弹出、 后台弹出悬浮窗 、允许系统修改、读取通知栏等权限(必须同意) */ fun setRingPermission(context: Context): Boolean { perArray.clear() if (!OpPermissionUtils.checkPermission(context)) { //跳转到悬浮窗设置 toRequestFloatWindPermission(context) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.System.canWrite(context)) { //准许系统修改 opWriteSetting(context) } if (!isAllowed(context)) { //后台弹出权限 openSettings(context) } if (!notificationListenerEnable(context)) { //通知使用权 gotoNotificationAccessSetting() } if (perArray.size != 0) { context.startActivities(perArray.toTypedArray()) return false } else { LogUtils.e("铃声 高级权限全部同意") return true } } /** * 点击授权按钮,编辑好需要申请的权限后,统一跳转,oppo/小米 的后台弹出权限 锁屏显示权限, * 需要用户去设置中手动开始,在项目中 可以使用 蒙层引导用户点击 */ fun setRingPermission(context: Context): Boolean { perArray.clear() if (!OpPermissionUtils.checkPermission(context)) { //跳转到悬浮窗设置 toRequestFloatWindPermission(context) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.System.canWrite(context)) { //准许系统修改 opWriteSetting(context) } if (!isAllowed(context)) { //后台弹出权限 openSettings(context) } if (!notificationListenerEnable(context)) { //通知使用权 gotoNotificationAccessSetting() } if (perArray.size != 0) { context.startActivities(perArray.toTypedArray()) return false } else { LogUtils.e("铃声 高级权限全部同意") return true } } /** * 申请悬浮窗权限 */ private fun toRequestFloatWindPermission(context: Context) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val clazz: Class<*> = Settings::class.java val field = clazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION") val intent = Intent(field[null].toString()) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.data = Uri.parse("package:" + context.packageName) perArray.add(intent) return } val intent2 = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION) context.startActivity(intent2) return } catch (e: Exception) { if (RomUtils.checkIsMeizuRom()) { try { val intent = Intent("com.meizu.safe.security.SHOW_APPSEC") intent.putExtra("packageName", context.packageName) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK context.startActivity(intent) } catch (e: java.lang.Exception) { LogUtils.e("请在权限管理中打开悬浮窗管理权限") } } LogUtils.e("请在权限管理中打开悬浮窗管理权限") return } } /** * 判断锁屏显示 */ private fun isLock(context: Context): Boolean { if (RomUtils.checkIsMiuiRom()) { return MiuiUtils.canShowLockView(context) } else if (RomUtils.checkIsVivoRom()) { return VivoUtils.getVivoLockStatus(context) } return true } /** * 判断锁屏显示 */ private fun isAllowed(context: Context): Boolean { if (RomUtils.checkIsMiuiRom()) { return MiuiUtils.isAllowed(context) } else if (RomUtils.checkIsVivoRom()) { return VivoUtils.getvivoBgStartActivityPermissionStatus(context) } return true } /** * 打开设置(后台弹出 锁屏显示) */ private fun openSettings(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { try { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.data = Uri.parse("package:${context.packageName}") perArray.add(intent) } catch (e: java.lang.Exception) { LogUtils.e("请在权限管理中打开后台弹出权限") } } else { LogUtils.e("android 6.0以下") } } /** * 系统修改 */ private fun opWriteSetting(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.System.canWrite(context)) { val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.data = Uri.parse("package:${context.packageName}") perArray.add(intent) } } } /** * 读取系统通知 */ private fun gotoNotificationAccessSetting() { try { val intent = Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS") intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK perArray.add(intent) } catch (e: ActivityNotFoundException) { try { val intent = Intent() intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK val cn = ComponentName("com.android.settings", "com.android.settings.Settings\$NotificationAccessSettingsActivity"); intent.component = cn intent.putExtra(":settings:show_fragment", "NotificationAccessSettings") perArray.add(intent) } catch (ex: Exception) { LogUtils.e("获取系统通知失败 e : $ex") } } } // 暂时把重要代码cv出来了一部分,建议下载Demo源码 ,结合博客一起观看
上述代码 主要罗列了需要引导用户开启部分设置权限的核心代码和方法。
监听电话
对于监听电话这块,会有很多兼容性的问题,我们这里先使用广播监听 action = android.intent.action.PHONE_STATE 的广播,然后根据状态调用起来悬浮窗。但是测试Android高版本手机 发现 InCallService 会更好的获取到电话状态,所以我这里的处理方案是 两个方案都保存在了代码中,最后通过调用不同的界面来区分。
BroadcastReceiver +悬浮窗显示实现
# AndroidManifest.xml // 监听电话状态广播 注册 <receiver android:name=".phone.receiver.PhoneStateReceiver"> <intent-filter android:priority="2147483647"> <action android:name="android.intent.action.NEW_OUTGOING_CALL" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <intent-filter android:priority="2147483647"> <action android:name="android.intent.action.PHONE_STATE" /> </intent-filter> <intent-filter android:priority="2147483647"> <action android:name="android.intent.action.DUAL_PHONE_STATE" /> </intent-filter> <intent-filter android:priority="2147483647"> <action android:name="android.intent.action.PHONE_STATE_2" /> </intent-filter> <intent-filter android:priority="2147483647"> <action android:name="com.cootek.smartdialer.action.PHONE_STATE" /> </intent-filter> <intent-filter android:priority="2147483647"> <action android:name="com.cootek.smartdialer.action.INCOMING_CALL" /> </intent-filter> </receiver>
# PhoneStateReceiver.kt class PhoneStateReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { context?.let { val action = intent?.action if (Intent.ACTION_NEW_OUTGOING_CALL == action || TelephonyManager.ACTION_PHONE_STATE_CHANGED == action) { try { val manager = it.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager var state = manager.callState val phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER) if (Intent.ACTION_NEW_OUTGOING_CALL.equals(action, true)) { state = 1000 } dealWithCallAction(state, phoneNumber) } catch (e: Exception) { } } } } //来去电的几个状态 private fun dealWithCallAction(state: Int?, phoneNumber: String?) { when (state) { // 来电状态 - 显示悬浮窗 TelephonyManager.CALL_STATE_RINGING -> { PhoneStateActionImpl.instance.onRinging(phoneNumber) } // 空闲状态(挂断) - 关闭悬浮窗 TelephonyManager.CALL_STATE_IDLE -> { PhoneStateActionImpl.instance.onHandUp() } // 摘机状态(接听) - 保持不作操作 TelephonyManager.CALL_STATE_OFFHOOK -> { PhoneStateActionImpl.instance.onPickUp(phoneNumber) } 1000 -> { //拨打电话广播状态 - 显示悬浮窗 PhoneStateActionImpl.instance.onCallOut(phoneNumber) } } } }
获取到广播的信息后 我们就可以着手 悬浮窗的绘制和 初始化工作
# FloatingWindow.kt private fun initView() { windowManager = mContext?.getSystemService(Context.WINDOW_SERVICE) as WindowManager params = WindowManager.LayoutParams() //高版本适配 全面/刘海屏 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } params.gravity = Gravity.CENTER params.width = WindowManager.LayoutParams.MATCH_PARENT params.height = WindowManager.LayoutParams.MATCH_PARENT params.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT params.format = PixelFormat.TRANSLUCENT // 设置 Window flag 为系统级弹框 | 覆盖表层 params.type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE // 去掉FLAG_NOT_FOCUSABLE隐藏输入 全面屏隐藏虚拟物理按钮办法 params.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN or WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN params.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_FULLSCREEN val interceptorLayout: FrameLayout = object : FrameLayout(mContext!!) { override fun dispatchKeyEvent(event: KeyEvent): Boolean { if (event.action == KeyEvent.ACTION_DOWN) { if (event.keyCode == KeyEvent.KEYCODE_BACK) { return true } } return super.dispatchKeyEvent(event) } } phoneCallView = LayoutInflater.from(mContext).inflate(R.layout.view_phone_call, interceptorLayout) tvCallNumber = phoneCallView.findViewById(R.id.tv_call_number) tvPhoneHangUp = phoneCallView.findViewById(R.id.tv_phone_hang_up) tvPhonePickUp = phoneCallView.findViewById(R.id.tv_phone_pick_up) tvCallingTime = phoneCallView.findViewById(R.id.tv_phone_calling_time) tvCallRemark = phoneCallView.findViewById(R.id.tv_call_remark) } ... // 部分代码省略
悬浮窗展示完成后,就要设置电话接通和挂断的操作(注意:这里很多低版本手机存在兼容问题,所以会有一些代码比较奇怪)
# IPhoneCallListenerImpl.kt override fun onAnswer() { val mContext = App.context try { val intent = Intent(mContext, ForegroundActivity::class.java) intent.action = CallListenerService.ACTION_PHONE_CALL intent.putExtra(CallListenerService.PHONE_CALL_ANSWER, "0") intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK mContext.startActivity(intent) } catch (e: Exception) { Log.e("ymc","startForegroundActivity exception>>$e") PhoneCallUtil.answer() } } override fun onOpenSpeaker() { PhoneCallUtil.openSpeaker() } override fun onDisconnect() { Log.e("ymc"," onDisconnect") val mContext = App.context try { val intent = Intent(mContext, ForegroundActivity::class.java) intent.action = CallListenerService.ACTION_PHONE_CALL intent.putExtra(CallListenerService.PHONE_CALL_DISCONNECT, "0") intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK mContext.startActivity(intent) } catch (e: Exception) { Log.e("ymc","startForegroundActivity exception>>$e") PhoneCallUtil.disconnect() } }
以上代码为接口实现类,我们这里会跳转到 一个前台Activity(一定程度上可以将App拉活),主要逻辑我们放在自己的前台Service中操作。
# CallListenerService.kt // Andorid新版本 启动服务的方式 fun forceForeground(intent: Intent) { try { ContextCompat.startForegroundService(App.context, intent) notification = CustomNotifyManager.instance?.getNotifyNotification(App.context) if (notification != null) { startForeground(CustomNotifyManager.STEP_COUNT_NOTIFY_ID, notification) } else { startForeground(CustomNotifyManager.STEP_COUNT_NOTIFY_ID, CustomNotifyManager.instance?.getDefaultNotification(NotificationCompat.Builder(App.context))) } } catch (e: Exception) { } } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (intent == null) { return START_STICKY } val action = intent.action ?: return START_STICKY when (action) { ACTION_PHONE_CALL -> { dispatchAction(intent) } } return START_STICKY } private fun dispatchAction(intent: Intent) { if (intent.hasExtra(PHONE_CALL_DISCONNECT)) { PhoneCallUtil.disconnect() return } if (intent.hasExtra(PHONE_CALL_ANSWER)) { PhoneCallUtil.answer() } }
为保证我们的服务能够正常吊起来,吊起前台服务,并设置Service等级,代码如下:
# AndroidManifest.xml <!-- 电话状态接收广播 --> <service android:name=".phone.service.CallListenerService" android:enabled="true" android:exported="false"> <intent-filter android:priority="1000"> <action android:name="com.maiya.call.phone.service.CallListenerService" /> </intent-filter> </service> <!-- 监听通知栏权限 必备 --> <service android:name=".phone.service.NotificationService" android:label="@string/app_name" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> <intent-filter> <action android:name="android.service.notification.NotificationListenerService" /> </intent-filter> </service>
低版本的接通和挂断电话,因为需要兼容部分机型,所以我们会有比较多的判断,代码如下:
# PhoneCallUtil.kt /** * 接听电话 */ fun answer() { when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> { val telecomManager = App.context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager if (ActivityCompat.checkSelfPermission(App.context, Manifest.permission.ANSWER_PHONE_CALLS) != PackageManager.PERMISSION_GRANTED) { return } telecomManager.acceptRingingCall() } Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> { finalAnswer() } else -> { try { val method: Method = Class.forName("android.os.ServiceManager") .getMethod("getService", String::class.java) val binder = method.invoke(null, Context.TELEPHONY_SERVICE) as IBinder val telephony = ITelephony.Stub.asInterface(binder) telephony.answerRingingCall() } catch (e: Exception) { finalAnswer() } } } } private fun finalAnswer() { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { val mediaSessionManager = App.context.getSystemService("media_session") as MediaSessionManager val activeSessions = mediaSessionManager.getActiveSessions(ComponentName(App.context, NotificationService::class.java)) as List<MediaController> if (activeSessions.isNotEmpty()) { for (mediaController in activeSessions) { if ("com.android.server.telecom" == mediaController.packageName) { mediaController.dispatchMediaButtonEvent(KeyEvent(0, 79)) mediaController.dispatchMediaButtonEvent(KeyEvent(1, 79)) break } } } } } catch (e: Exception) { e.printStackTrace() answerPhoneAidl() } } private fun answerPhoneAidl() { try { val keyEvent = KeyEvent(0, 79) val keyEvent2 = KeyEvent(1, 79) if (Build.VERSION.SDK_INT >= 19) { @SuppressLint("WrongConstant") val audioManager = App.context.getSystemService("audio") as AudioManager audioManager.dispatchMediaKeyEvent(keyEvent) audioManager.dispatchMediaKeyEvent(keyEvent2) } } catch (ex: java.lang.Exception) { val intent = Intent("android.intent.action.MEDIA_BUTTON") intent.putExtra("android.intent.extra.KEY_EVENT", KeyEvent(0, 79) as Parcelable) App.context.sendOrderedBroadcast(intent, "android.permission.CALL_PRIVILEGED") val intent2 = Intent("android.intent.action.MEDIA_BUTTON") intent2.putExtra("android.intent.extra.KEY_EVENT", KeyEvent(1, 79) as Parcelable) App.context.sendOrderedBroadcast(intent2, "android.permission.CALL_PRIVILEGED") } } /** * 断开电话,包括来电时的拒接以及接听后的挂断 */ fun disconnect() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { with(PhoneCallManager.instance) { if (!hasDefaultCall()) { return@with } mainCallId?.let { val result = disconnect(it) if (result) { return } } } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { val telecomManager = App.context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager if (ActivityCompat.checkSelfPermission(App.context, Manifest.permission.ANSWER_PHONE_CALLS) != PackageManager.PERMISSION_GRANTED) { return } telecomManager.endCall() } else { try { val method: Method = Class.forName("android.os.ServiceManager") .getMethod("getService", String::class.java) val binder = method.invoke(null, Context.TELEPHONY_SERVICE) as IBinder val telephony = ITelephony.Stub.asInterface(binder) telephony.endCall() } catch (e: Exception) { e.printStackTrace() } } }
到这里中低版本的电话接通和挂断,基本已经完毕。下一步 我们主要写,用户在同意设置应用为默认电话应用后的 更加简单方便的实现方式。
InCallService + Activity实现
在使用 InCallService 服务的同时,需要设置该应用为默认拨号应用 (这里只说明技术的可能性,不对用户行为分析)。
# AndroidManifest.xml <!-- 电话service --> <service android:name=".phone.service.PhoneCallService" android:permission="android.permission.BIND_INCALL_SERVICE"> <!-- name为自己的Service名字,per和 filter中的name为固定值 --> <intent-filter> <action android:name="android.telecom.InCallService" /> </intent-filter> <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" /> </service>
# PhoneCallService.kt @RequiresApi(Build.VERSION_CODES.M) class PhoneCallService : InCallService() { companion object { const val ACTION_SPEAKER_ON = "action_speaker_on" const val ACTION_SPEAKER_OFF = "action_speaker_off" const val ACTION_MUTE_ON = "action_mute_on" const val ACTION_MUTE_OFF = "action_mute_off" fun startService(action: String?) { val intent = Intent(App.context, PhoneCallService::class.java).apply { this.action = action } App.context.startService(intent) } } // Call 添加 (Call对象需要判断是否有多个呼入的情况) override fun onCallAdded(call: Call?) { super.onCallAdded(call) call?.let { it.registerCallback(callback) PhoneCallManager.instance.addCall(it) } } // Call 移除 (可以理解为某一个通话的结束) override fun onCallRemoved(call: Call?) { super.onCallRemoved(call) call?.let { it.unregisterCallback(callback) PhoneCallManager.instance.removeCall(it) } } override fun onCanAddCallChanged(canAddCall: Boolean) { super.onCanAddCallChanged(canAddCall) PhoneCallManager.instance.onCanAddCallChanged(canAddCall) } // 将Call CallBack放在PhoneCallManager类中统一处理 private val callback: Call.Callback = object : Call.Callback() { override fun onStateChanged(call: Call?, state: Int) { super.onStateChanged(call, state) PhoneCallManager.instance.onCallStateChanged(call, state) } override fun onCallDestroyed(call: Call) { call.hold() super.onCallDestroyed(call) } } // 设置扬声器 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { ACTION_SPEAKER_ON -> setAudioRoute(CallAudioState.ROUTE_SPEAKER) ACTION_SPEAKER_OFF -> setAudioRoute(CallAudioState.ROUTE_EARPIECE) ACTION_MUTE_ON -> setMuted(true) ACTION_MUTE_OFF -> setMuted(false) else -> { } } return super.onStartCommand(intent, flags, startId) } }
以上为InCallService的代码。部分方法进行了说明。
# PhoneCallManager.kt /** * 接听电话 */ @RequiresApi(Build.VERSION_CODES.M) fun answer(callId: String?) = getCallById(callId)?.let { it.answer(VideoProfile.STATE_AUDIO_ONLY) true } ?: false /** * 断开电话,包括来电时的拒接以及接听后的挂断 */ @RequiresApi(Build.VERSION_CODES.M) fun disconnect(callId: String?) = getCallById(callId)?.let { it.disconnect() true } ?: false
由于篇幅问题,PhoneCallManager中的代码不全部展示,需要的小伙伴请移步Github,该类中主要进行了一些默认拨号应用,呼叫Call是否保持等一些操作。
以上就是Android 自定义来电秀实现总结的详细内容,更多关于Android 自定义来电秀的资料请关注我们其它相关文章!