共计 19641 个字符,预计需要花费 50 分钟才能阅读完成。
前言
要想实现自定义 复电秀,首先咱们先这样 再这样,而后你这样,最初你再这样一下,就能够了,很好实现的,听懂了么?
效果图
TODO
- 增加包活 lib,进步 App 在设置胜利后 退居后盾,胜利拉起的概率
- 我的项目中曾经蕴含 lib_ijk 的代码,咱们能够增加视频复电展现,增加美女或者豪车等全屏视频,成果更佳。
- 因为反编译能力无限,对于多种机型权限的跳转(后续能够开起 无障碍服务,间接一步搞定多种须要用户手动设置操作)
- 该 Demo 中有一部分不欠缺的 Rom 权限跳转机制,后续还须要工夫来欠缺。
实现思维
- 通过监听手机 Service 分辨复电状态,而后弹出咱们自定义的复电页面,笼罩零碎复电页面。
- 通过相干 API(次要两种:读取复电零碎的 Notification 信息 和 模仿耳机线控的形式进行挂断 / 接听)实现接听和挂断性能。我这里会应用两种(低版本 应用电话状态播送监听,高版本应用 InCallService)监听电话状态的 Service 及两种界面展现 来出现复电信息,多个界面和多个 Service 的监听 可能减少高版本的容错率兼容性。
- 实现自定义的拨号界面 或者 间接应用零碎的拨号界面。
申请权限
动态权限
电话利用,会用到很多权限,我这里尽可能多的动态注册了一些权限,如果引入我的项目中,须要甄别下,代码如下:
<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 是否放弃等一些操作。
最初
到这里这篇文章根本曾经写得差不多了,在本人编写 Demo 的时候也观看了很多其余的自定义复电秀博客,并且反编译了一些市面上不错的复电秀 App,如果有哪里侵权的中央,私信沟通,我会进行批改。感激大家可能观看我的 开发笔记总结。
更多 Android 技术分享能够关注 @我, 也能够退出 QQ 群号:Android 进阶学习群:345659112,一起学习交换。
作者:小肥羊冲冲冲 Android
链接:https://juejin.cn/post/701242…
起源:掘金
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。