如何在Android 5.0(Lollipop)中以编程方式回复来电?

  发布于 2022-12-11 12:56

当我尝试为来电创建自定义屏幕时,我正在尝试以编程方式接听来电.我使用以下代码但它在Android 5.0中不起作用.

// Simulate a press of the headset button to pick up the call
Intent buttonDown = new Intent(Intent.ACTION_MEDIA_BUTTON);             
buttonDown.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
context.sendOrderedBroadcast(buttonDown, "android.permission.CALL_PRIVILEGED");

// froyo and beyond trigger on buttonUp instead of buttonDown
Intent buttonUp = new Intent(Intent.ACTION_MEDIA_BUTTON);               
buttonUp.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));
context.sendOrderedBroadcast(buttonUp, "android.permission.CALL_PRIVILEGED");

Valter Janso.. 137

使用Android 8.0 Oreo进行更新

虽然最初问的问题是Android L支持,但人们似乎仍然在回答这个问题和答案,所以值得描述Android 8.0 Oreo中引入的改进.向后兼容的方法仍在下面描述.

改变了什么?

从Android 8.0 Oreo开始,PHONE权限组还包含ANSWER_PHONE_CALLS权限.正如权限的名称所示,持有它允许您的应用程序通过正确的API调用以编程方式接受传入的调用,而不会使用反射或模拟用户对系统进行任何黑客攻击.

我们如何利用这一变化?

如果您支持较旧的Android版本,则应在运行时检查系统版本,以便在保持对旧版Android版本的支持的同时封装此新API调用.您应该在运行时按照请求权限在运行时获取该新权限,这是较新的Android版本的标准.

获得许可后,您的应用只需调用TelecomManager的acceptRingingCall方法即可.基本调用如下所示:

TelecomManager tm = (TelecomManager) mContext
        .getSystemService(Context.TELECOM_SERVICE);

if (tm == null) {
    // whether you want to handle this is up to you really
    throw new NullPointerException("tm == null");
}

tm.acceptRingingCall();

方法1:TelephonyManager.answerRingingCall()

当您无限制地控制设备时.

这是什么?

TelephonyManager.answerRingingCall()是一个隐藏的内部方法.它作为ITelephony.answerRingingCall()的桥梁,已经在互联网上进行了讨论,并且在一开始就很有希望.它在4.4.2_r1上不可用,因为它仅在Android 4.4 KitKat的提交83da75d(4.4.3_r1中的第1537行)中引入,后来在Lollipop的提交f1e1e77中 "重新引入"(5.038_r1上的第3138行),原因在于Git树是结构化的.这意味着,除非你只支持Lollipop的设备,这可能是一个基于其目前微小市场份额的糟糕决定,你仍然需要提供后备方法,如果走这条路线.

我们怎么用这个?

由于所讨论的方法对SDK应用程序的使用是隐藏的,因此您需要使用反射来在运行时动态检查和使用该方法.如果您不熟悉反射,可以快速阅读什么是反射,为什么它有用?.如果您对此感兴趣,您还可以深入了解Trail:The Reflection API中的细节.

那在代码中看起来如何?

// set the logging tag constant; you probably want to change this
final String LOG_TAG = "TelephonyAnswer";

TelephonyManager tm = (TelephonyManager) mContext
        .getSystemService(Context.TELEPHONY_SERVICE);

try {
    if (tm == null) {
        // this will be easier for debugging later on
        throw new NullPointerException("tm == null");
    }

    // do reflection magic
    tm.getClass().getMethod("answerRingingCall").invoke(tm);
} catch (Exception e) {
    // we catch it all as the following things could happen:
    // NoSuchMethodException, if the answerRingingCall() is missing
    // SecurityException, if the security manager is not happy
    // IllegalAccessException, if the method is not accessible
    // IllegalArgumentException, if the method expected other arguments
    // InvocationTargetException, if the method threw itself
    // NullPointerException, if something was a null value along the way
    // ExceptionInInitializerError, if initialization failed
    // something more crazy, if anything else breaks

    // TODO decide how to handle this state
    // you probably want to set some failure state/go to fallback
    Log.e(LOG_TAG, "Unable to use the Telephony Manager directly.", e);
}

这太好了,不可能!

实际上,有一个小问题.此方法应该完全正常,但安全管理器希望调用者持有android.permission.MODIFY_PHONE_STATE.此权限仅限于部分记录的系统功能,因为不希望第三方接触它(正如您可以从文档中看到的那样).您可以尝试添加for,但这样做没有用,因为此权限的保护级别是签名系统(请参阅5.0.0_r1上core/AndroidManifest的第1201行).

你可以阅读问题34785:更新android:protectLevel文档,这是在2012年创建的,看看我们是否缺少有关特定"管道语法"的详细信息,但是通过试验,它似乎必须起到'AND'的作用,意味着所有的必须满足指定的标志才能授予权限.在这个假设下工作,这意味着你必须有你的申请:

    安装为系统应用程序.

    这应该没问题,可以通过要求用户在恢复中使用ZIP进行安装来完成,例如在已经打包的自定义ROM上生成或安装Google应用程序时.

    签名与框架/ base相同的签名,即系统,即ROM.

    这就是弹出问题的地方.要做到这一点,您需要掌握用于签署框架/基础的密钥.您不仅需要访问Google的Nexus工厂映像密钥,而且还必须访问所有其他OEM和ROM开发人员的密钥.这看似不合理,因此您可以通过制作自定义ROM并要求用户切换到它(可能很难)或通过查找可以绕过权限保护级别的漏洞利用系统密钥对应用程序进行签名(也可能很难).

此外,这种行为似乎是相关发行34792:安卓果冻豆/ 4.1:android.permission.READ_LOGS不再工作这与无证开发标志一起使用相同的保护级别为好.

使用TelephonyManager听起来不错,但除非获得适当的许可,否则无法使用,这在实践中并不容易.

那么在其他方面使用TelephonyManager呢?

可悲的是,它似乎要求你持有android.permission.MODIFY_PHONE_STATE来使用酷工具,这反过来意味着你将很难获得对这些方法的访问.


方法2:服务呼叫SERVICE CODE

当您可以测试设备上运行的构建是否可以使用指定的代码时.

如果无法与TelephonyManager交互,还可以通过service可执行文件与服务进行交互.

这是如何运作的?

这很简单,但是关于这条路线的文档比其他文档更少.我们确信可执行文件包含两个参数 - 服务名称和代码.

我们要使用的服务名称phone.

这可以通过运行来看出service list.

我们想要使用的代码似乎是6但现在似乎是5.

现在看来它已经基于IBinder.FIRST_CALL_TRANSACTION + 5用于许多版本(从1.5_r4到4.4.4_r1),但在本地测试期间,代码5用于接听来电.由于Lollipo是一个大规模的更新,所以在这里改变是可以理解的内部.

这导致了一个命令service call phone 5.

我们如何以编程方式使用它?

Java的

以下代码是一个粗略的实现,用作概念证明.如果你真的想继续使用这种方法,你可能想要查看无问题su使用指南,并可能切换到Chainfire更完全开发的libsuperuser.

try {
    Process proc = Runtime.getRuntime().exec("su");
    DataOutputStream os = new DataOutputStream(proc.getOutputStream());

    os.writeBytes("service call phone 5\n");
    os.flush();

    os.writeBytes("exit\n");
    os.flush();

    if (proc.waitFor() == 255) {
        // TODO handle being declined root access
        // 255 is the standard code for being declined root for SU
    }
} catch (IOException e) {
    // TODO handle I/O going wrong
    // this probably means that the device isn't rooted
} catch (InterruptedException e) {
    // don't swallow interruptions
    Thread.currentThread().interrupt();
}

表现



这真的需要root访问吗?

可悲的是,它似乎如此.您可以尝试使用Runtime.exec,但我无法获得该路线的运气.

这有多稳定?

我很高兴你问.由于没有记录,这可以突破各种版本,如上面的看似代码差异所示.服务名称应该可以在各种构建中保持联系,但是对于我们所知道的,代码值可以在相同版本的多个构建中进行更改(例如,通过OEM的外观进行内部修改),从而破坏所使用的方法.因此值得一提的是,测试是在Nexus 4(mako/occam)上进行的.我个人建议你不要使用这种方法,但由于我无法找到更稳定的方法,我相信这是最好的方法.


原始方法:耳机键码意图

适合你必须安顿下来的时候.

以下部分受到Riley C的回答的强烈影响.

在原始问题中发布的模拟耳机意图方法似乎正如人们所期望的那样进行广播,但它似乎没有实现接听电话的目标.虽然似乎存在应该处理这些意图的代码,但它们根本就没有被关注,这意味着必须采取某种新的对策来对付这种方法.该日志也没有显示出任何有趣的内容,我个人认为通过Android源代码挖掘这一点是值得的,因为谷歌可能会引入一些轻微破坏所用方法的轻微变化.

我们现在能做些什么吗?

可以使用输入可执行文件一致地重现该行为.它接受一个keycode参数,我们只需传入KeyEvent.KEYCODE_HEADSETHOOK.该方法甚至不需要root访问权限,使其适用于普通公众的常见用例,但该方法存在一个小缺点 - 无法指定耳机按钮按下事件需要权限,这意味着它像真实一样工作按下按钮和气泡向上穿过整条产业链,而这又意味着你必须要谨慎,什么时候按下按钮模拟,因为它可以,例如,触发音乐播放器开始播放,如果没有其他人更高的优先级已准备好处理事件.

码?

new Thread(new Runnable() {

    @Override
    public void run() {
        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));

            mContext.sendOrderedBroadcast(btnDown, enforcedPerm);
            mContext.sendOrderedBroadcast(btnUp, enforcedPerm);
        }
    }

}).start();

TL;博士

Android 8.0 Oreo及更高版本有一个很好的公共API.

Android 8.0 Oreo之前没有公共API.内部API是禁止的或只是没有文档.你应该谨慎行事.

撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有