温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Android源码个个击破之拨号源码

发布时间:2020-06-07 15:25:47 来源:网络 阅读:1403 作者:屠夫章哥 栏目:移动开发

#############需求#############

最近我的一位同事大概是利用下面的方法监听电话的状态

        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文件

        Android源码个个击破之拨号源码    

然后我同事说遇到两个问题

        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工具Android源码个个击破之拨号源码):

                    InCallActivity.java

        现在,我们需求是想自己实现拨号级来电接听,不走系统UI 。

        所以,我的思路是先确定Intent.ACTION_CALL是被谁处理了,怎么处理的,如何调起InCallActivity,InCallActivity又干了啥

  •   InCallActivity被谁打开的

     在xref全局搜索,发现InCallPresenter.java这个类

     Android源码个个击破之拨号源码

    通过官方的解释,可以知道此类正是管理通话状态, 开启InCallActivity。

    搜索InCallPresenter 又会被很多类调用,可见此类是集中处理通话状态的核心类。其中有 InCallServiceImpl,InCallServiceImpl注册在dialer的清单文件里:

    Android源码个个击破之拨号源码



    二、屏蔽原始UI

              1.


    • https://blog.csdn.net/zuiaikg703/article/details/8178028  

     电话的UI都是通过InCallActivity添加Fragment实现的,所以,只要把其UI屏蔽掉就可以了。(修改源码要一针见血,找到最佳修改位置。这样既能减少工作量又能避免修改错误)

                

              2.但是来电时显示的那个悬浮窗是如何出现的呢?如何屏蔽掉?

              https://blog.csdn.net/u012439416/article/details/78946337

              Android源码个个击破之拨号源码

              首先,得从“来电”来出发寻找,其实这个UI就叫做StatusBarNotifier

              在InCallPresenter里找到一些蛛丝马迹:

               Android源码个个击破之拨号源码

                看看官方对此类的解释

                Android源码个个击破之拨号源码

                准备把下面的方法注释掉,应该就不会显示通知栏

                Android源码个个击破之拨号源码

                经测试,注释上面的方法,确实可以屏蔽来电时显示的通知栏。

  

    三、主动拨号之后无法挂断的问题

            在(二.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的布局显示出来,果然,是有个系统的拨打电话的界面。

                Android源码个个击破之拨号源码

             那么如何隐藏并这个activity界面并屏蔽它的所有时间

             1)将InCallActivity的theme变成透明

                  Android源码个个击破之拨号源码

                       

                    Android源码个个击破之拨号源码

             2)屏蔽InCallActivity的点击事件

                     在InCallActivity的onCreate方法里加上下面代码

          this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);

             3)屏蔽InCallActivity对状态栏的改变

                             

      

        对系统电话的call方法发起追踪研究:

        1.找service

        Android源码个个击破之拨号源码

          

          Android源码个个击破之拨号源码

            

            注意Context.TELEPHONY_SERVICE的值是“phone”

            Android源码个个击破之拨号源码

             通过搜索“extends ITelephony.Stub ”或者“phone”可以找到ITelephony的Service类/packages/services/Telephony/src/com/android/phone/PhoneInterfaceManager.java:

             可以看到这个service

             Android源码个个击破之拨号源码

             上面的init方法在PhoneGlobals的onCreate方法里被调用

              Android源码个个击破之拨号源码

           2.分析service的相应call方法

              Android源码个个击破之拨号源码

                

                Android源码个个击破之拨号源码

               可以看到最终也是通过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;

    

                 



      

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI