#############需求#############
最近我的一位同事大概是利用下面的方法监听电话的状态
https://www.pocketdigi.com/20110725/417.html
相关demo下载链接:
本来我是将系统的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://www.jianshu.com/p/ca4ab4e9817f/ (一个电话应用,实际由多个代码模块组成。由dialer下的Android.mk可以看出)
(介绍各个模块的作用)
一、拨号
假如我不想调用系统的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.
电话的UI都是通过InCallActivity添加Fragment实现的,所以,只要把其UI屏蔽掉就可以了。(修改源码要一针见血,找到最佳修改位置。这样既能减少工作量又能避免修改错误)
2.但是来电时显示的那个悬浮窗是如何出现的呢?如何屏蔽掉?
首先,得从“来电”来出发寻找,其实这个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”
“phone”可以找到ITelephony的Service类/packages/services/Telephony/src/com/android/phone/PhoneInterfaceManager.java:
可以看到这个service
上面的init方法在PhoneGlobals的onCreate方法里被调用
2.分析service的相应call方法
↓↓↓
可以看到最终也是通过Intent去打开拨号页面的。
、主动拨号之后对方没有接听直接挂断,ITelephony没有回调的问题。
此种情况虽然ITelephony没有回调,但是系统会有个无人接听的提示语音,可以在此处修改系统源码,人工发一个空闲状态广播。
、去电,PhoneListener的状态瞎回调的问题。
错误的回调。需要人为的去处理,可以根据时间来过滤判断,但是具有不可靠性。
通话中状态。但有时候不会回调。
- 空闲的状态很好解决:
点击拨打电话的按钮时,将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;
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
通话中状态回调不准确的问题则不好解决,但是经测试,发现系统电话应用关于电话状态的判断则十分准确,只有从源码入手了。
网络上搜到的关于通话中状态判断的解决方案:
事实证明此方法行不通,拨打电话还没有通话就回调了。
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;
转载于:https://blog.51cto.com/4259297/2372997