#############需求#############
专注于为中小企业提供成都网站建设、成都做网站服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业阿城免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了成百上千企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。
最近我的一位同事大概是利用下面的方法监听电话的状态
https://www.pocketdigi.com/20110725/417.html
https://blog.csdn.net/lyen2010/article/details/42590099
相关demo下载链接:https://download.csdn.net/download/weiyirong/6872889
本来我是将系统的ITelephony.aidl复制进去,但是编译会报not fount the import class xx错误,于是看了一下上面的demo,对aidl作了简化:
interface ITelephony { boolean endCall(); void answerRingingCall(); boolean enableDataConnectivity(); boolean disableDataConnectivity(); boolean isDataConnectivityPossible(); }
aidl文件及目录建好,重新rebuild工程,就会在对应的build目录下生成对应的java文件
然后我同事说遇到两个问题
1.电话接通之后,无法挂断
2.通话状态无法正常显示(准确的说是:通话中的状态不显示)
我写了个测试demo
挂断方法:
public void hangup_call(View view) { Log.e(TAG,"hangup_call START"); ... }
接通电话之后,点击挂断按钮:无响应,日志也没有打。
我隐隐约约感觉线程阻塞了,果然接听加上线程就解决了问题。上面的问题就是接听电话阻塞了主线程,所以挂断按钮点击不了,广播也阻塞了。
#############源码研究#############
以下源码基于android6.0.1
拨号源码详解:
https://so.csdn.net/so/search/s.do?q=Android6.0%E7%9A%84phone%E5%BA%94%E7%94%A8%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90&t=blog&u=u014386544
拨号源码架构:
https://www.jianshu.com/p/ca4ab4e9817f/ (一个电话应用,实际由多个代码模块组成。由dialer下的Android.mk可以看出)
https://blog.csdn.net/wds1181977/article/details/61920067 (介绍各个模块的作用)
一、拨号
假如我不想调用系统的INTENT来打电话(最直接的方法就是调用ITelephony,文章开头已经介绍过),那么就得对系统打电话的INTENT做一番探索了。
通过调用Intent.ACTION_CALL,会打开系统的拨号引用。
向外拨号,发现当前显示的activity是(通过sdk工具):
InCallActivity.java
现在,我们需求是想自己实现拨号级来电接听,不走系统UI 。
所以,我的思路是先确定Intent.ACTION_CALL是被谁处理了,怎么处理的,如何调起InCallActivity,InCallActivity又干了啥。
InCallActivity被谁打开的
在xref全局搜索,发现InCallPresenter.java这个类
通过官方的解释,可以知道此类正是管理通话状态, 开启InCallActivity。
搜索InCallPresenter 又会被很多类调用,可见此类是集中处理通话状态的核心类。其中有 InCallServiceImpl,InCallServiceImpl注册在dialer的清单文件里:
二、屏蔽原始UI
1.
https://blog.csdn.net/zuiaikg703/article/details/8178028
电话的UI都是通过InCallActivity添加Fragment实现的,所以,只要把其UI屏蔽掉就可以了。(修改源码要一针见血,找到最佳修改位置。这样既能减少工作量又能避免修改错误)
2.但是来电时显示的那个悬浮窗是如何出现的呢?如何屏蔽掉?
https://blog.csdn.net/u012439416/article/details/78946337
首先,得从“来电”来出发寻找,其实这个UI就叫做StatusBarNotifier
在InCallPresenter里找到一些蛛丝马迹:
看看官方对此类的解释:
准备把下面的方法注释掉,应该就不会显示通知栏
经测试,注释上面的方法,确实可以屏蔽来电时显示的通知栏。
三、主动拨号之后无法挂断的问题
在(二.1),屏蔽IncallUI之后,发现主动往外打电话之后,其他的按钮没法点击。
public void call(View view) { Log.d(TAG, "call: "); /* //取得一个Callintent final Intent intent = CallUtil.getCallIntent("13121116227"); //交给DialerUtils去处理 DialerUtils.startActivityWithErrorToast(this, intent);*/ callThread = new Thread(){ @Override public void run() { try { ITelephony iTelephony = PhoneUtils.getITelephony((TelephonyManager)MainActivity.this.getSystemService( Context.TELEPHONY_SERVICE)); iTelephony.call(getPackageName(),"17316107592"); } catch (Exception e) { Log.e(TAG, "[Broadcast]Exception111="+e.getMessage(), e); } } }; callThread.start(); }
所以我猜想有2种原因:
1)打电话时InCallActivity弹出来了(眼睛看不见),挡住了我的Activity
2)系统电话的call方法阻塞了UI线程
是否是系统的UI挡住了我的Activity:
将上面的InCallActivity的布局显示出来,果然,是有个系统的拨打电话的界面。
那么如何隐藏并这个activity界面并屏蔽它的所有时间
1)将InCallActivity的theme变成透明
↓↓↓
2)屏蔽InCallActivity的点击事件
在InCallActivity的onCreate方法里加上下面代码
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
3)屏蔽InCallActivity对状态栏的改变
对系统电话的call方法发起追踪研究:
1.找service
↓↓↓
↓↓↓
注意Context.TELEPHONY_SERVICE的值是“phone”
通过搜索“extends ITelephony.Stub ”或者“phone”可以找到ITelephony的Service类/packages/services/Telephony/src/com/android/phone/PhoneInterfaceManager.java:
可以看到这个service
上面的init方法在PhoneGlobals的onCreate方法里被调用
2.分析service的相应call方法
↓↓↓
可以看到最终也是通过Intent去打开拨号页面的。
三、主动拨号之后对方没有接听直接挂断,ITelephony没有回调的问题。
此种情况虽然ITelephony没有回调,但是系统会有个无人接听的提示语音,可以在此处修改系统源码,人工发一个空闲状态广播。
四、去电,PhoneListener的状态瞎回调的问题。
去电时:android6.0系统会立马发一个通话中和空闲的状态,这些其实都是错误的回调。需要人为的去处理,可以根据时间来过滤判断,但是具有不可靠性。
一般对方接通电话之后,会再回调一个正确的通话中状态。但有时候不会回调。
第一个错误的空闲的状态很好解决:
点击拨打电话的按钮时,将Constants.isPhoneOut设置为true。
case TelephonyManager.CALL_STATE_IDLE: // 空闲 0 Log.d(tag, "--------------------------CALL_STATE_IDLE00---------------------isPhoneOut = " + Constants.isPhoneOut); if(Constants.isPhoneOut == true){ //拨打电话时,系统会误发一个挂断的广播,需要自己区分处理 Constants.isPhoneOut = false; return; }
但是如果主动往外拨打,对方没有接通或者挂断,是不会收到空闲回调的。上面这种判断就会有缺陷,所以准备找到拨打电话的源码,去掉拨打电话前误发的空闲状态。
发现底层的回调是这样的:
07-15 17:16:28.591 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: NO_CALLS -> NO_CALLS 07-15 17:16:28.666 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: OUTGOING -> OUTGOING 07-15 17:16:28.725 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: OUTGOING -> PENDING_OUTGOING 07-15 17:16:28.774 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: PENDING_OUTGOING -> INCALL 07-15 17:16:28.791 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: INCALL -> INCALL 07-15 17:16:28.819 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: INCALL -> INCALL 07-15 17:16:28.828 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: INCALL -> INCALL 07-15 17:16:28.830 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: INCALL -> INCALL 07-15 17:16:29.111 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: INCALL -> OUTGOING 07-15 17:16:30.776 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: OUTGOING -> OUTGOING 07-15 17:16:30.833 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: OUTGOING -> OUTGOING 07-15 17:16:30.849 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: OUTGOING -> OUTGOING 07-15 17:16:30.863 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: OUTGOING -> OUTGOING 07-15 17:16:30.878 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: OUTGOING -> OUTGOING 07-15 17:16:30.892 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: OUTGOING -> OUTGOING 07-15 17:18:13.357 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: OUTGOING -> INCALL 07-15 17:18:13.416 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: INCALL -> INCALL 07-15 17:18:15.404 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: INCALL -> NO_CALLS 07-15 17:18:15.448 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: NO_CALLS -> NO_CALLS 07-15 17:18:15.499 5456-5456/com.android.dialer I/InCall: InCallPresenter - Phone switching state22222: NO_CALLS -> NO_CALLS
拨打电话,系统立马会发 NO_CALLS -> NO_CALLS状态,也就是app会无缘无故收到空闲状态的原因。但是这个状态不好限制。
等了很久,系统会发送OUTGOING -> INCALL,也就是通话中状态,这也是个误发。app不应做处理。
最后会发个INCALL -> NO_CALLS状态,也就是对方未接听或者拒接后系统的一个超时回调,这里可以发一个空闲广播。app处理这个空闲广播,屏蔽api的空闲回调即可解决空闲问题。
但是此时mInCallActivity为null,所以无法通过mInCallActivity发空闲广播,改由mContext发送广播。经验证,mContext果然不为空。
**Log.d在logcat上不输出日志,但是Log.i可以输出日志。**
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
但是通话中状态回调不准确的问题则不好解决,但是经测试,发现系统电话应用关于电话状态的判断则十分准确,只有从源码入手了。
网络上搜到的关于通话中状态判断的解决方案:https://blog.csdn.net/qwe749082787/article/details/78364112
https://blog.csdn.net/qwe749082787/article/details/78364112 (可以修改源码,此处发一个广播),事实证明此方法行不通,拨打电话还没有通话就回调了。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
突然我脑洞大开,查看了一下电话应用com.android.dialer的日志,发现,在InCallPresenter类里,在来电接听会有系统日志:
Phone switching state: INCOMING -> INCALL
去电时会有系统日志:
Phone switching state: OUTGOING -> INCALL
而且这个日志不会重复,可以在上面发送两个自定义广播,就能准确监听到通话中状态了。
在测试中发现 OUTGOING -> INCALL,这个不一定只有去电通话中才会回调,需要结合前一个状态变化联合判断:
所以修改InCallPresenter类,缓存上一次的通话状态变化,然后对比方能准确判断通话中的状态
InCallState newState = getPotentialStateFromCallList(callList); InCallState oldState = mInCallState; Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState); newState = startOrFinishUi(newState); Log.d(this, "onCallListChange newState changed to " + newState); String oldStateLast = phone_state_cacheSp.getString("oldState", ""); String newStateLast = phone_state_cacheSp.getString("newState", ""); // Set the new state before announcing it to the world Log.i(this, "Phone switching state22222: " + oldState + " -> " + newState); SharedPreferences.Editor editor = phone_state_cacheSp.edit(); editor.putString("oldState",oldState.toString()); editor.putString("newState",newState.toString()); editor.commit(); if(mInCallActivity != null){ if("INCOMING".equals(oldState.toString()) && "INCALL".equals(newState.toString())){ mInCallActivity.sendBroadcast(new Intent("CZ_IN")); }else if("OUTGOING".equals(oldState.toString()) && "INCALL".equals(newState.toString())){ if("PENDING_OUTGOING".equals(oldStateLast) && "OUTGOING".equals(newStateLast)){ Log.i(this, "Phone switching state22222: " + "这是个假的通话中状态,舍弃"); return; } mInCallActivity.sendBroadcast(new Intent("CZ_OUT")); } } mInCallState = newState;