Android通过程序接听或者挂断电话

作者: 十个雨点 | 来源:发表于2016-09-11 17:29 被阅读10085次

    转载注明出处:简书-十个雨点

    这篇文章教你如何帮助用户自动接听或者挂断来电。当然并不是我原创的代码,我只不过是把stackoverflow上的一些代码整合了一下,做个代码的二传手。

    源码

    AcceptOrRejectCallDemo
    源码中用了MVP的模式,只是最简单的使用,如果不熟悉的话刚好可以学学,逻辑部分在IncomingPresenter类中。

    首先需要监听来电的广播

    在AndroidManifest文件中添加:

    <receiver android:name=".incomingcall.PhoneListener">
        <intent-filter android:priority="1000">
            <action android:name="android.intent.action.PHONE_STATE" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </receiver>
    

    当然还有权限:

        <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    

    然后实现PhoneListener来实现来电监听:

    
    public class PhoneListener extends BroadcastReceiver {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Log.e("PhoneListener",action);
            if (action.equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
            } else {
                TelephonyManager tm = (TelephonyManager) context
                        .getSystemService(Service.TELEPHONY_SERVICE);
                String incoming_number = "";
                switch (tm.getCallState()) {
                    case TelephonyManager.CALL_STATE_RINGING:
                        incoming_number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
                        try {
                            //3s后再开启activity,是为了挡在系统的接听界面之前
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Intent tmpI = new Intent(context, IncomingCallActivity.class);
                        tmpI.putExtra(IncomingCallActivity.INCOMING_CALL_NAME,incoming_number);
                        tmpI.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        context.startActivity(tmpI);
                        break;
                    case TelephonyManager.CALL_STATE_OFFHOOK:
                        break;
                    case TelephonyManager.CALL_STATE_IDLE:
                        break;
                }
            }
        }
    }
    
    

    然后就是接听或者挂断了

    先说挂断

    新建aidl文件:

    package com.android.internal.telephony;
    interface ITelephony{
        boolean endCall();
        void answerRingingCall();
    }
    

    要注意包路径不能修改,要放置到正确路径下才行哦。

    然后在需要挂断的时候,调用:

        public void rejectCall() {
            try {
                Method method = Class.forName("android.os.ServiceManager")
                        .getMethod("getService", String.class);
                IBinder binder = (IBinder) method.invoke(null, new Object[]{Context.TELEPHONY_SERVICE});
                ITelephony telephony = ITelephony.Stub.asInterface(binder);
                telephony.endCall();
            } catch (NoSuchMethodException e) {
                Log.d(TAG, "", e);
            } catch (ClassNotFoundException e) {
                Log.d(TAG, "", e);
            } catch (Exception e) {
            }
        }
    
    

    很简单吧,其实就是通过反射的方式盗用Android系统的aidl通信来实现挂断的功能。

    再说接听

    看到挂断电话的代码后大家都能猜到,只要同样调用aidl的answerRingingCall()方法就可以接听了,是的,在4.1一下的版本中只有这样就行了:

        public void acceptCall() {
            try {
                Method method = Class.forName("android.os.ServiceManager")
                        .getMethod("getService", String.class);
                IBinder binder = (IBinder) method.invoke(null, new Object[]{Context.TELEPHONY_SERVICE});
                ITelephony telephony = ITelephony.Stub.asInterface(binder);
                telephony.answerRingingCall();
            } catch (Exception e) {
                Log.e(TAG, "for version 4.1 or larger");
                acceptCall_4_1();
            }
        }
    

    可惜在4.1以上的版本中,谷歌给这个方法的调用设置了权限,如果不是系统应用,会收到permissDeny的异常。
    不过没关系,这当然难不倒我们,看代码:

        private static final String MANUFACTURER_HTC = "HTC";
        public void acceptCall_4_1() {
            //模拟无线耳机的按键来接听电话
            // for HTC devices we need to broadcast a connected headset
            boolean broadcastConnected = MANUFACTURER_HTC.equalsIgnoreCase(Build.MANUFACTURER)
                    && !audioManager.isWiredHeadsetOn();
            if (broadcastConnected) {
                broadcastHeadsetConnected(false);
            }
            try {
                try {
                    Runtime.getRuntime().exec("input keyevent " +
                            Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK));
                } catch (IOException e) {
                    // Runtime.exec(String) had an I/O problem, try to fall back
                    String enforcedPerm = "android.permission.CALL_PRIVILEGED";
                    Intent btnDown = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra(
                            Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN,
                                    KeyEvent.KEYCODE_HEADSETHOOK));
                    Intent btnUp = new Intent(Intent.ACTION_MEDIA_BUTTON).putExtra(
                            Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP,
                                    KeyEvent.KEYCODE_HEADSETHOOK));
                    view.getActivity().sendOrderedBroadcast(btnDown, enforcedPerm);
                    view.getActivity().sendOrderedBroadcast(btnUp, enforcedPerm);
                }
            } finally {
                if (broadcastConnected) {
                    broadcastHeadsetConnected(false);
                }
            }
        }
        
        private void broadcastHeadsetConnected(boolean connected) {
            Intent i = new Intent(Intent.ACTION_HEADSET_PLUG);
            i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            i.putExtra("state", connected ? 1 : 0);
            i.putExtra("name", "mysms");
            try {
                view.getActivity().sendOrderedBroadcast(i, null);
            } catch (Exception e) {
            }
        }
            
    

    看代码很容易看出来,这里实际上是通过模拟无线耳机的按键来接听电话的。而对HTP的手机,需要进行一点特殊的处理,也就是通过广播的形式,让手机误以为连上了无线耳机。

    以上功能在6.0的三星S6上实测过,也在许多其他我能拿到的手机上测试过,都可以正常挂断和接听。不过没测试过的手机肯定更多,如果无法接听可以给我留言,咱们一起研究。
    不过请首先确保只是接听或者挂断功能不正常,而不是监听来电的功能不正常。

    我们做自动接听或者挂断的功能的时候,有可能是没有界面展示的,而是在service中直接就帮用户接听了,这种情况下是无法正常工作的,可能是android对非前台的应用做了一些限制。
    怎么办呢?其实只要创建一个Activity就行了:

    1. 监听到来电的时候,创建并显示一个1px*1px大小的空Activity;
    2. 在这个Activity中调用上述的acceptCall()方法;
    3. 监听到电话状态改变(被接听或者被挂断)以后,finish()掉这个Activity。

    ps:最近看到7.0中新加了CallScreeningService,可以直接实现拒接的功能了,不过接听似乎还是无法实现的......

    psps:最近看到8.0中新加了权限

    Android 8.0 引入了多个与电话有关的新权限:

    • ANSWER_PHONE_CALLS 允许您的应用通过编程方式接听呼入电话。要在您的应用中处理呼入电话,您可以使用 [acceptRingingCall()](https://developer.android.com/reference/android/telecom/TelecomManager.html#acceptRingingCall()) 函数。
    • READ_PHONE_NUMBERS 权限允许您的应用读取设备中存储的电话号码。

    这些权限均被划分为危险类别,属于 PHONE 权限组

    这样一来,8.0中就可以直接接电话了,期待国产厂商发力普及

    相关文章

      网友评论

      • 谁不低头莫敢回首:7.1开始电话挂断,号码识别都好着,就唯独接电话不行,
      • AFAP:CallScreeningService如何使用实现挂断电话呢?
      • henhaoji:怎么挂断的都一笔带过,测试大部分手机(7.0,8.0)上都要权限; Exception java.lang.SecurityException: MODIFY_PHONE_STATE permission required.
        Pencilso:我也遇到这个问题了
        Soul1010:@十个雨点 MODIFY_PHONE_STATE这个是系统权限,除了内置应用不可能能获得这个权限的,什么叫很容易取得!!!
        十个雨点:@henhaoji 这个权限很容易取得的啊,只是普通权限而已
      • f625a3cd6a91:8.0系统和模拟机都不能实现自动接听
      • 王洪贺:另外楼主所说的7.0增加的这个CallSreeningService类确切的说应该是来电过滤的类,并不能实现随时挂断电话的功能。四个重要的方法:setDisallowCall(), setRejectCall(), setSkipCallLog(), setSkipNotification(), 从这4个方法也可以大概知道,这个“来电过滤”能做的具体的事情是什么:1. 完全阻止来电(类似黑名单),2. 是否拒接来电, 3. 是否在CallLog中显示, 4. 是否要显示未接通知,也就是说 可以通过这4个方法设置来电的提醒方式。而目前我们的需求时,来电话了,用户可以通过其它方式,例如遥控器或者语音控制来处理当前的通话(接听或者挂断),而不是在代码里在来电话前设置好了是否要过滤掉对应号码的接听规则
      • 王洪贺:最近更新了8.0的系统,接电话好用了,但是通过反射挂断电话又不行了,只在android8.0之前可用,另外楼主说的这些接电话的方法其实我都尝试过,在2016年10月份之前基本都可以使用
        runtime.exec("input keyevent " +KeyEvent.KEYCODE_HEADSETHOOK);实现,但是后来更新了安全补丁,只有得到root权限才可以执行这个命令。
        建议你可以通过读取通知栏权限来触发notification的pendingIntent来实现接挂电话的功能,但也只是适配一部分手机,我测试过华为、小米、锤子、魅族、中兴都可以,三星、oppo、vivo不可以。
        具体可以看我的博客
        http://www.cnblogs.com/dongweiq/p/5882408.html
        十个雨点:@王洪贺 这些方法都越来越不能用了,确实很蛋疼
      • 大表哥007:这个Demo封装的有点屌,让我看了半天才理清逻辑
        十个雨点:@大表哥007 写得比较随意,不好看
      • KingGu:楼主你好。小米设备接听来电不行。7.0系统,现在有办法解决吗?
        十个雨点:@KingGu 目前没有找到解决办法
      • KingGu:小米mix2 7.0无法接听电话哦。楼主有能在7.0上实现自动接听的方法吗
        十个雨点:@KingGu 7.0上我不清楚怎么做,目前还没找到方法
      • RedRainM:6.0以上 不能实现接听 是的么
        十个雨点:@RedRainM 是的
      • RedRainM:华为6..0的接听电话不可以
      • RedRainM:老铁 可以实现5.0 6.0自动接听的功能么
      • 盗梦如画:楼主我在 oppo r9m 上实现挂断电话 无法实现此功能, 客户反映什么至尊宝牌子(系统是阿里云)的手机也是挂断不了电话
        盗梦如画:获取手机通话记录也获取不到
      • 一只呆萌的程序猿:楼主,为啥我这边接听不了呢,只能挂断。
        十个雨点:在7.0以上似乎接听失效了
      • a8f926977662:android 7.1.1 三星C7 测试挂断可以,接听无响应,求解
        十个雨点:@crosswp 应该是7.0把这个漏洞修复了,导致无法接听了,目前还没有找到好方法
        a8f926977662:另MODIFY_PHONE_STATE这个权限不是也限制了吗?求解
      • 盗梦如画:这个怎么使用啦
      • 墨迹的托儿索:请问楼主,android7可以么
        墨迹的托儿索:@十个雨点 大神,有新的方案不
        墨迹的托儿索:@十个雨点 :sob: 怎么办,怎么办
        十个雨点:不可以了呢
      • a36a9fcacf12:已经魔改了这个项目了(把设计模式拆分成自己类库组合模式)、而且改的过程中发现有个类多余的。。。。
        不过这个接通电话有这种操作真是服了。。给个赞。现在在想怎么实现自动接通并留言。
        系统读取一段自定义留言播放->然后开始录制音频->对方挂机停止录制。我就可以听留言啦。。
        好想实现不过好像有难度。。。。
        6.0的权限要用户确定的这里没动态权限申请,所以报权限错误的手动设置权限把。。。
        十个雨点:确实比较难呢
      • itarbo:挂断功能直接报异常说没有权限:Neither user 10056 nor current process has android.permission.PHONE_CALL,但是我在清单文件里面已经添加了呀,试了两部是手机都不行,分别是6.0 和 7.0 的系统
        Wlazly_bingo:@itarbo 你在应用程序管理里面有给权限吗
        十个雨点:@itarbo 你看看应用程序管理里面有给权限吗?7.0的应该是不行了,6.0我用三星是可以的
        itarbo:手机分别是乐视和华为
      • yangguanghaozi:楼主大神,请教个问题,我的需求如下:
        我用的是锤子m1l,锁屏状态下如果收到短信或者微信新消息时,会屏幕唤醒并弹出一个com.android.systemui.popup.PopUpStatusBar,我想实现一个service,该service在监听到手机晃动之后将将这些PopUpStatusBar关闭。
        现在我实现了监听手机晃动,但是不知道怎么关闭这些系统的PopUpStatusBar,请指条明路,感谢。
        十个雨点:想去操作系统的窗口,如果没有高级权限还是比较难的吧,毕竟安卓的权限控制越来越严格了。有些弹框可能会在新开activity的时候消失,比如QQ长按聊天气泡的菜单,如果是这种就可以开个Activity再关闭。但是锁屏界面上的框估计不能这么搞。。。所以总的来说,我还是不知道咋弄
      • 雨间落寞:Vivo貌似也不行 都是6.0的系统,不
        8dc8b6651c0b:@十个雨点 Vivo X9
        十个雨点:具体是什么型号的?
      • 78c0abfd09e0:miui 8 不行, 我也是通过这样的方式接听电话的,只是不知道小米改了什么东西
        十个雨点:@78c0abfd09e0 这个可能越来越难了,毕竟是找系统的漏洞
        78c0abfd09e0:@十个雨点 现在我还没找到别的方式去实现
        十个雨点:机型适配真是大问题
      • 白衣雨果:非常好,楼主辛苦了。
      • qqjunji:楼主好人啊,能不能把你实现的源码发我一份啊,本人菜鸟一枚,想学习一下
        十个雨点:@qqjunji 你在收到来的广播的时候,将系统的电话音量设置成0,然后挂断电话以后再回复就可以静音了吧。不显示通话界面可能比较难,因为系统的通话界面无论如何都会显示的,除非你立刻挂断或者接听,或者像我的demo里面的做法一样,开启一个新的activity把系统的界面给挡住。
        qqjunji:@十个雨点 楼主,怎么实现来电时静音啊,而且不要显示通话界面
        十个雨点:@qqjunji 在github上,原文中有链接,很醒目的
      • 十个雨点:这里有一篇文章可能可以实现:http://blog.csdn.net/tyfjy/article/details/6452126
      • Jin丶破:请问下博主,有没有方法监听到主拨被接通的实现震动的效果(当然,震动可以换成其他操作)
      • 862c9fb99650:研究一下
      • pseudo_niaonao:虽然一楼说没意义,,那是相对市场说的,没人会搞一个接听电话的程序,因为新手机发布系统都自带,但对学习者自己是有一定帮助的,楼主加油
        pseudo_niaonao:@十个雨点 也行
        十个雨点:@_niaonao 其实有些使用场景会有这样的需求,比如开车导航应用,语音辅助控制应用等等。。。自己学习的同时,给需要的朋友准备着<( ̄ˇ ̄)/
      • dfe147f4a102:有机会研究研究

      本文标题:Android通过程序接听或者挂断电话

      本文链接:https://www.haomeiwen.com/subject/zdawsttx.html