使用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) {
throw new NullPointerException("tm == null");
}
tm.acceptRingingCall();
方法一:TelephonyManager.answerRingingCall()
适用于您完全控制设备的情况。
这是什么?
有一个TelephonyManager.answerRingingCall()方法,它是一个隐藏的内部方法。它作为ITelephony.answerRingingCall()的桥梁,后者已经在互联网上讨论并在开始时似乎很有前途。它在4.4.2_r1上不可用,因为它只在Android 4.4 KitKat(4.4.3_r1上的第1537行)中引入了83da75d提交,并在Lollipop(5.0.0_r1上的第3138行)的f1e1e77提交中“重新引入”,因为Git树的结构方式不同。这意味着,除非您只支持Lollipop设备(考虑到其目前市场份额很小,这可能是一个糟糕的决定),否则仍然需要提供回退方法。
如何使用?
由于该方法被隐藏在SDK应用程序中,因此您需要使用反射在运行时动态地检查和使用该方法。如果您不熟悉反射,可以快速阅读什么是反射,它为什么有用?。如果您对此感兴趣,还可以深入了解Trail:反射API的具体细节。
那么在代码中看起来怎样?
final String LOG_TAG = "TelephonyAnswer";
TelephonyManager tm = (TelephonyManager) mContext
.getSystemService(Context.TELEPHONY_SERVICE);
try {
if (tm == null) {
throw new NullPointerException("tm == null");
}
tm.getClass().getMethod("answerRingingCall").invoke(tm);
} catch (Exception e) {
Log.e(LOG_TAG, "Unable to use the Telephony Manager directly.", e);
}
这也太好了吧!
实际上,有一个小问题。这种方法应该是完全可用的,但安全管理器希望调用者持有android.permission.MODIFY_PHONE_STATE权限。
这个权限属于系统的部分文档化功能,不建议第三方接触它(从其文档中可以看出)。你可以尝试添加一个<uses-permission>
,但那样没有任何作用,因为这个权限的保护级别是signature|system(请参见5.0.0_r1版本的core/AndroidManifest第1201行)。
你可以阅读2012年创建的
Issue 34785: 更新android:protectionLevel文档,以查看我们缺少有关特定“管道语法”的详细信息,但从实验中可以看出,它必须作为“AND”运行,这意味着所有指定的标志都必须满足才能授予权限。基于这个假设,这意味着你的应用程序必须:
安装为系统应用程序。
这应该是可以的,可以通过要求用户在恢复模式下使用ZIP进行安装,例如在root或安装Google应用程序的自定义ROM上,这些应用程序没有被预先打包。
使用与frameworks/base(即系统,即ROM)相同的签名。
这就是问题所在。要做到这一点,您需要掌握用于签署frameworks/base的密钥。您不仅需要获得Nexus出厂映像的Google密钥,还需要获得所有其他OEM和ROM开发人员的密钥。这似乎不太可能,因此您可以通过制作自定义ROM并要求用户切换到它(这可能很难),或者找到一个可以绕过权限保护级别的漏洞来签署系统密钥(这也可能很难)。
此外,这种行为似乎与Issue 34792: Android Jelly Bean / 4.1: android.permission.READ_LOGS no longer works有关,该问题利用了相同的保护级别以及一个未记录的开发标志。
与TelephonyManager一起工作听起来不错,但是如果您没有获得适当的权限,这将无法实现,在实践中这并不容易。
那么在其他方面使用TelephonyManager呢?
可悲的是,似乎需要您持有android.permission.MODIFY_PHONE_STATE才能使用这些酷工具,这又意味着您将很难访问这些方法。
方法二:服务调用服务代码
当您可以测试设备上运行的构建是否与指定的代码兼容时使用。
如果无法与TelephonyManager交互,则还可以通过service
可执行文件与服务进行交互。
这是如何工作的?
这相当简单,但比其他路线的文档还要少。我们确定可执行文件需要传入两个参数 - 服务名称和代码。
这将使用命令
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) {
}
} catch (IOException e) {
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
清单
<!
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
这真的需要root权限吗?
很遗憾,似乎是这样。你可以尝试在其上使用Runtime.exec,但我并没有成功。
这有多稳定?
很高兴你问了这个问题。由于没有文档记录,这可能会因版本不同而破坏,正如上面所示的代码差异。服务名称应该保持为phone,但我们都知道,代码值可以在同一版本的多个构建中(例如OEM皮肤的内部修改)发生变化,进而破坏所使用的方法。因此,值得一提的是,测试是在Nexus 4(mako/occam)上进行的。个人建议你不要使用这种方法,但由于我无法找到更稳定的方法,我认为这是最好的尝试。
原始方法:耳机按键代码意图
在你不得不妥协的时候。
以下部分受到Riley C的this answer强烈影响。
在原始问题中发布的模拟耳机意图方法似乎像预期的那样广播,但它似乎无法实现接听电话的目标。虽然已经有了处理这些意图的代码,但它们并没有被关注,这必须意味着有一些新的对抗措施针对这种方法。日志也没有显示任何有趣的内容,我个人认为挖掘Android源代码并不值得,因为Google可能会轻微更改,很容易就会破坏使用的方法。
现在我们能做些什么吗?
该行为可以使用输入可执行文件持续重现。它需要一个键代码参数,我们只需传入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) {
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();
简述
Android 8.0 Oreo及以上版本有一个很好的公共API。
Android 8.0 Oreo之前没有公共API。内部API是禁止访问的,或者没有文档。您应该小心谨慎。