android12 手机通话监听 Service Broadcast Worker Notification TelephonyManager SubscriptionManager

业务需求: 

需要CRM系统对接手机app, 让员工通过app连接到CRM系统, 让CRM下发呼叫指令, 让手机来电可以第一时间通知CRM弹屏客户资料信息. 

开发目标:

1. 通过外部系统, 发送号码及指令到手机端,  让手机发起呼叫. 

2. 手机呼入, 发送http请求外部系统, 告知什么号码来电.

3. (来电/去电)通话结束后, 采集通话记录发送到外部系统

4. 录音上传外部系统(未实现)

呼入呼出流程设计:

接收外部呼叫消息 -> 通过new Intent(Intent.ACTION_CALL, Uri.parse("tel:"+num))跳转到系统呼叫UI发起呼叫.  通话结束后, 利用广播接收器收到系统挂机消息, 获取通话记录通过http发到外部系统. 外呼流程结束.

手机来电开始响铃 - 广播接收器收到响铃消息, 通过http发送来电号码到外部系统. 挂机后,  接收器收到挂机消息, 获取通话记录发到外部系统. 呼入流程结束.

根据流程设计需要用到四个模块:

BroadcastReceiver: 接收器, 接收系统通知(响铃、挂机,  系统启动)

Service: app服务, 为了能在UI关闭后仍然运行, 大部分逻辑要在服务里实现.

Actitity: UI,  状态展示与输入

Worker: 耗时的网络请求都放在Worker里执行. 

程序实现: 

安装首次打开app,  会先集中授权所有权限,  当权限全部完成之后, 首先获取手机卡1号码(早期版本用TelephonyManager, 新版本用SubscriptionManager), 携带号码启动服务startService(). 

服务首次启动, 首先在onCreate 里注册广播接收器, 用于接收系统广播.  接着在onStartCommand里就可以创建通知频道并启动前台服务startForeground(). 为了满足后面没有UI的时候, 启动也能拿到手机号码, 需要把传入的手机号存入sharedPreferences里, 用于程序自启动时读取.  建立socket连接用于与外部系统通信. 

等待socket连接成功, 通过Worker 完成http请求外部系统, 在成功回调可发送local广播通知前台UI更新状态展示. 

----此时服务启动完成-----

外部系统通过websocket发送socket到手机, 需要手机调出拨号程序,  但android高版本权限限制原因, 不能直接从service 打开 activity, 因此采用通知方式解决:

用new Intent(Intent.ACTION_CALL, Uri.parse("tel:"+text) 创建的 PendingIntent生成一个notification, 这样点击通知的时候, 就可以直接唤出呼叫UI. 为了更明显的提醒, 需要通知使用铃声并且需要横幅通知.  这两项配置在代码里写了并不能直接使用, 需要用户手动在app设置里打开, 所以UI上需要一个按钮打开这个app setting UI.

电话呼入首先响应的是广播接收器, 通过action android.intent.action.PHONE_STATE 接收后通过 TelephonyManager实例调用 getCallState() 可以区分 响铃、接听和挂机.  在响铃的case中, 调用Worker 发送请求告知外部系统, 有电话呼入, 外部系统可以根据自己需要做处理(比如弹屏显示客户信息). 

最后就是挂机, 一开始一直纠结直接通过广播来生成通话记录, 后来发现外呼的时候, 客户是否接听, 没有收到任何广播, 通过求助后得知可以用 CallLog可以查询到通话记录, 那就可以在每次挂机的时候, 读取最新一条通话记录数据.  猜测CallLog也是通过接收到挂机广播才写入数据的, 所以如果在接收挂机广播的时候, 立刻读取CallLog, 是查不到最新一条数据的. 但是CallLog写入完成又没查到有回调或者广播能感知, 所以只能用延时1秒调用的方式读取CallLog最新通话记录并通过worker发送至外部系统.

相关代码:

获取卡1号码

    public static String getLine1Num(Context context, String TAG){
        String line1Num;

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            line1Num = telephonyManager.getLine1Number();
        }else{
            SubscriptionManager subscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                line1Num = subscriptionManager.getPhoneNumber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
            }else{
                SubscriptionInfo info = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(0);
                line1Num = info.getNumber();
            }
        }
        Log.i(TAG, "line1Number:"+line1Num);
        return line1Num;
    }

广播接收OnReceive

    @Override
    public void onReceive(Context context, Intent intent) {

        Log.i(TAG, "onReceive: "+intent.getAction());
        if(intent.getAction().equals(PHONE_STATE_RECEIVED)) {
      
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值