Android 来电秀总结

Android 来电秀总结,第1张

概述该文章为对工作中部分业务实现的总结,阅读时间:20分钟,版本:Android6.0-9.0updatetime2021年02月03日11:48:55文章可能存在不足之处,还望评论批评,一起学习进步。前言要想实现自定义来电秀,首先我们先这样再这样,然后你这样,最后你再这样一下,就可以了,很好实现的,听懂了么

该文章为对工作中部分业务实现的总结,阅读时间:20分钟,版本:AndroID 6.0 - 9.0
update time 2021年02月03日11:48:55
文章可能存在不足之处,还望评论批评,一起学习进步。

前言

要想实现自定义 来电秀,首先我们先这样 再这样,然后你这样,最后你再这样一下,就可以了,很好实现的,听懂了么?-,-

实现思想通过监听手机Service 分辨来电状态,然后d出我们自定义的来电页面,覆盖系统来电页面。通过相关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,所以请求权限直接请求分组中的全部权限了,项目中根据需要动态申请部分权限

  虽然我们已经申请了这么多权限,但是为了能够替换系统电话界面成功,还有一部分权限是需要通过d框来引导用户去 设置中开启的。

# CallerShowPermissionManager.kt/**     * 判断是否有 锁屏d出、 后台d出悬浮窗 、允许系统修改、读取通知栏等权限(必须同意)     */    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)) {            //后台d出权限            openSettings(context)        }        if (!notificationListenerEnable(context)) {            //通知使用权            gotoNotificationAccessSetting()        }        if (perArray.size != 0) {            context.startActivitIEs(perArray.toTypedArray())            return false        } else {            LogUtils.e("铃声 高级权限全部同意")            return true        }    }/**     * 点击授权按钮,编辑好需要申请的权限后,统一跳转,oppo/小米 的后台d出权限 锁屏显示权限,     * 需要用户去设置中手动开始,在项目中 可以使用 蒙层引导用户点击     */    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)) {            //后台d出权限            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    }    /**     * 打开设置(后台d出 锁屏显示)     */    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("请在权限管理中打开后台d出权限")            }        } 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.ktclass 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.ktprivate 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 为系统级d框 | 覆盖表层        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.ktoverrIDe 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是否保持等一些 *** 作。

后续更新方向添加包活lib,提高App在设置成功后 退居后台,成功拉起的概率项目中已经包含lib_ijk的代码,我们可以添加视频来电展示,添加美女或者豪车等全屏视频,效果更佳。由于反编译能力有限,对于多种机型权限的跳转(后续可以开起 无障碍服务,直接一步搞定多种需要用户手动设置 *** 作)该Demo中有一部分不完善的Rom 权限跳转机制,后续还需要时间来完善。最后

到这里这篇文章基本已经写得差不多了,在自己编写Demo的时候也观看了很多其他的自定义来电秀博客,并且反编译了一些市面上不错的来电秀App,如果有哪里侵权的地方,私信沟通,我会进行修改。感谢大家能够观看我的 开发笔记总结。

效果图

总结

以上是内存溢出为你收集整理的Android 来电秀总结全部内容,希望文章能够帮你解决Android 来电秀总结所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1044639.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-24
下一篇 2022-05-24

发表评论

登录后才能评论

评论列表(0条)

保存