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


87

当我尝试为来电创建自定义屏幕时,我试图以编程方式接听来电。我正在使用以下代码,但在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");

哦,男人,为什么要碰到这个,只要滑动男人!对我来说似乎更容易\ m /
nobalG

我正在为Android用户创建自定义来电屏幕。
maveroid

2
任何人?我也对此感兴趣!尝试了很多东西,但是没有用:/
Arthur

1
@nobalG他以编程方式说
dsharew 2014年

1
@maveroid,您是否提出了针对Android 5.0的解决方法?
arthursfreire

Answers:


155

使用Android 8.0 Oreo更新

尽管最初要求提供Android L支持是一个问题,但似乎仍然有人在质疑这个问题和答案,因此值得描述Android 8.0 Oreo中引入的改进。向后兼容方法仍在下面描述。

发生了什么变化?

Android 8.0 Oreo开始PHONE权限组还包含ANSWER_PHONE_CALLS权限。就像许可名称所暗示的那样,持有该许可可以使您的应用通过适当的API调用以编程方式接受传入的调用,而无需使用反射或模拟用户对系统进行任何黑客攻击。

我们如何利用这一变化?

如果要支持较旧的Android版本,则应在运行时检查系统版本,以便可以封装此新的API调用,同时保持对这些较旧的Android版本的支持。您应该在运行时遵循请求权限,以在运行时获得新的权限,这是较新的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因为它被引入只有在提交83da75d为Android 4.4奇巧(上4.4.3_r1行1537)以及后来的“重新”在提交f1e1e77的棒棒堂(上5.0.0_r1线3138),由于该如何Git树结构化。这意味着,除非您仅支持使用Lollipop的设备,否则从目前的市场份额来看,这可能是一个错误的决定,否则,按照这种方法,您仍然需要提供备用方法。

我们将如何使用它?

由于有问题的方法对于SDK应用程序的使用是隐藏的,因此您需要使用反射在运行时动态检查和使用该方法。如果您不熟悉反射,可以快速阅读什么是反射,它为什么有用?。如果您愿意的话,还可以在Trail: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。此权限仅存在部分记录在案的系统功能,因为不希望第三方接触到该功能(如从其文档中所见)。您可以尝试<uses-permission>为其添加a ,但这将无济于事,因为此权限的保护级别为签名系统请参阅5.0.0_r1上core / AndroidManifest的1201行)。

您可以阅读问题34785:更新于2012年创建的android:protectionLevel文档,以了解我们缺少有关特定“管道语法”的详细信息,但通过试验,它似乎必须以“ AND”的形式起作用,这意味着所有必须授予指定标志才能授予许可。在该假设下工作,这意味着您必须拥有自己的申请:

  1. 作为系统应用程序安装。

    这应该没问题,可以通过要求用户在恢复中使用ZIP来完成,例如在尚未打包的自定义ROM上生根或安装Google应用时。

  2. 使用与框架/基础(即系统,即ROM)相同的签名进行签名。

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

此外,此行为似乎与问题34792有关:Android Jelly Bean / 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_r44.4.4_r1)的版本,但是在本地测试期间,代码5可以接听来电。由于Lollipo在各个方面都进行了大规模更新,因此内部的变化也可以理解。

结果为的命令service call phone 5

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

爪哇

以下代码是一个粗略的实现,用作概念证明。如果你真的想继续使用这个方法,你可能想看看自由问题苏的使用指南,并可能切换到更充分的发展libsuperuser通过Chainfire

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();
}

表现

<!-- Inform the user we want them root accesses. -->
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>

这真的需要root访问吗?

可悲的是,似乎是这样。您可以尝试在其上使用Runtime.exec,但是我无法通过该路由获得任何运气。

这有多稳定?

我很高兴你问。由于未记录在案,因此可能会跨越各种版本,如上面看似的代码差异所示。服务名称可能应该在各个版本之间保持联系,但就我们所知,代码值可能会在同一版本的多个版本(例如,OEM的皮肤内部修改)之间改变,从而破坏了所使用的方法。因此,值得一提的是该测试是在Nexus 4(mako / occam)上进行的。我个人不建议您使用此方法,但是由于找不到更稳定的方法,因此我认为这是最好的选择。


原始方法:耳机键码意图

有时需要定居。

下一节受Riley C的回答的影响很大。

最初问题中发布的模拟耳机意图方法似乎像人们期望的那样广播,但似乎并没有达到接听电话的目的。尽管似乎有适当的代码可以处理这些意图,但是根本就不在乎它们,这意味着必须有某种针对此方法的新对策。日志中也没有显示任何感兴趣的内容,我个人也不认为为此而在Android源代码中进行挖掘是值得的,这仅仅是因为Google可能会进行轻微的更改而轻易地改变了所使用的方法。

我们现在能做些什么吗?

可以使用输入可执行文件一致地重现该行为。它接受一个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; dr

Android 8.0 Oreo及更高版本有一个不错的公共API。

Android 8.0 Oreo之前没有公共API。内部API禁止使用,或者完全没有文档。您应谨慎行事。


关于耳机键码意图,您是否出于某种原因在这里签出了Google源代码?有趣的是,仍然可以通过这些意图轻松地拒绝呼叫(只需长按即可),但是没有任何电话可以接听。我还没有找到一个明确的权限检查或其他潜在的障碍,希望第二只眼睛可以发现一些东西。
Riley C

因此有点忙,因此延迟-将尝试花费一些时间来解决此问题。快速浏览后,似乎CallsManager构造了HeadsetMediaButton。那里的会话回调应该负责从MediaSessionManager回调时调用handleHeadsetHook(KeyEvent)。所有代码似乎都匹配...但是我不知道,有人可以删除KeyEvent.ACTION_DOWN意向进行测试吗?(也就是说,一次只能触发KeyEvent.ACTION_UP。)
Valter Jansons,2014年

实际上,HeadsetMediaButton与MediaSession一起使用,并且不直接与MediaSessionManager交互...
Valter Jansons 2014年

1
对于那些设法找到原始方法似乎无效的情况(例如,棒棒糖有问题)的人,我有个好消息和坏消息:我设法使ACTION_UP在我的代码中100%工作, FULL_WAKE_LOCK。它不适用于PARTIAL_WAKE_LOCK。绝对没有关于此原因的文档。当我更广泛地测试实验代码时,我将在以后的答案中详细介绍。坏消息是,当然,不建议使用FULL_WAKE_LOCK,因此,此修复程序仅在Google将其保留在API中的情况下才能持续。
leRobot 2015年

1
在很多情况下,原始答案的陷阱并不多见。我发现最好先打电话给exec,然后再打电话给按钮。
Warpzit '16

36

完整的解决方案基于@Valter Strods代码。

为了使其正常工作,您必须在执行代码的锁定屏幕上显示一个(不可见的)活动。

AndroidManifest.xml

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />

<activity android:name="com.mysms.android.lib.activity.AcceptCallActivity"
        android:launchMode="singleTop"
        android:excludeFromRecents="true"
        android:taskAffinity=""
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:theme="@style/Mysms.Invisible">
    </activity>

呼叫接受活动

package com.mysms.android.lib.activity;

import android.app.Activity;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.view.WindowManager;

import org.apache.log4j.Logger;

import java.io.IOException;

public class AcceptCallActivity extends Activity {

     private static Logger logger = Logger.getLogger(AcceptCallActivity.class);

     private static final String MANUFACTURER_HTC = "HTC";

     private KeyguardManager keyguardManager;
     private AudioManager audioManager;
     private CallStateReceiver callStateReceiver;

     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);

         keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
         audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
     }

     @Override
     protected void onResume() {
         super.onResume();

         registerCallStateReceiver();
         updateWindowFlags();
         acceptCall();
     }

     @Override
     protected void onPause() {
         super.onPause();

         if (callStateReceiver != null) {
              unregisterReceiver(callStateReceiver);
              callStateReceiver = null;
         }
     }

     private void registerCallStateReceiver() {
         callStateReceiver = new CallStateReceiver();
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         registerReceiver(callStateReceiver, intentFilter);
     }

     private void updateWindowFlags() {
         if (keyguardManager.inKeyguardRestrictedInputMode()) {
              getWindow().addFlags(
                       WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
                                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
                                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
         } else {
              getWindow().clearFlags(
                       WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
                                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
                                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
         }
     }

     private void acceptCall() {

         // 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 {
                  logger.debug("execute input keycode headset hook");
                  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
                  logger.debug("send keycode headset hook intents");
                  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));

                  sendOrderedBroadcast(btnDown, enforcedPerm);
                  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 {
              sendOrderedBroadcast(i, null);
         } catch (Exception e) {
         }
     }

     private class CallStateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
              finish();
         }
     }
}

风格

<style name="Mysms.Invisible">
    <item name="android:windowFrame">@null</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowContentOverlay">@null</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowAnimationStyle">@null</item>
</style>

终于叫魔术了!

Intent intent = new Intent(context, AcceptCallActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
context.startActivity(intent);

1
mi支持在“最终调用魔术”下添加代码。它可以在Android 6.0上使用

我在这里说的是broadcastHeadsetConnected(boolean connected)是解决Samsung A3 2016设备中问题的原因。否则,一个非常相似的方法(使用单独的透明活动以及用于调用和其他操作的线程)可以在大约20个经过测试的设备上完全运行,然后出现了A3,迫使我重新检查该问题以寻找新答案。与我的代码进行比较之后,那是明显的不同!
leRobot

1
我也该如何拒绝通话?您可以更新答案以显示此内容吗?
阿曼尼

@leRobot此答案检查是否是要广播的HTC设备HeadsetConnected,如何检查它是否是三星A3 2016设备?顺便说一句,这确实是一个很好的答案,即使屏幕被锁定,我的应用程序也可以接听电话。
eepty '16

@eepty您可以使用官方的设备参考来获取构建数据。(support.google.com/googleplay/answer/1727131?hl=zh_CN)。我将Build.MODEL.startsWith(“ SM-A310”)用于A32016。但是!我可以确认A3 2016不支持耳机连接的广播!什么是真正解决了我的问题是首先改变顺序,以便调用Runtime.getRuntime()EXEC(...触发这些设备似乎每一次工作的该设备,不会回落到异常。
leRobot

14

以下是对我有用的替代方法。它直接使用MediaController API将密钥事件发送到电信服务器。这要求该应用程序具有BIND_NOTIFICATION_LISTENER_SERVICE权限并被用户明确授予“通知”访问权限:

@TargetApi(Build.VERSION_CODES.LOLLIPOP) 
void sendHeadsetHookLollipop() {
    MediaSessionManager mediaSessionManager =  (MediaSessionManager) getApplicationContext().getSystemService(Context.MEDIA_SESSION_SERVICE);

    try {
        List<MediaController> mediaControllerList = mediaSessionManager.getActiveSessions 
                     (new ComponentName(getApplicationContext(), NotificationReceiverService.class));

        for (MediaController m : mediaControllerList) {
             if ("com.android.server.telecom".equals(m.getPackageName())) {
                 m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));
                 log.info("HEADSETHOOK sent to telecom server");
                 break;
             }
        }
    } catch (SecurityException e) {
        log.error("Permission error. Access to notification not granted to the app.");      
    }  
}

NotificationReceiverService.class 上面的代码中可能只是一个空类。

import android.service.notification.NotificationListenerService;

public class NotificationReceiverService extends NotificationListenerService{
     public NotificationReceiverService() {
     }
}

与清单中的相应部分:

    <service android:name=".NotificationReceiverService" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
        android:enabled="true" android:exported="true">
    <intent-filter>
         <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>

由于事件的目标是明确的,因此这可能应该避免触发媒体播放器的任何副作用。

注意:振铃事件后,电信服务器可能不会立即处于活动状态。为了使此操作可靠地运行,对于应用程序来说,在发送事件之前实现MediaSessionManager.OnActiveSessionsChangedListener来监视电信服务器何时处于活动状态可能很有用。

更新:

Android的Ø,一个需要模拟ACTION_DOWN之前ACTION_UP,否则上面没有任何影响。即需要以下内容:

m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));

但是,由于自Android O以来就可以进行正式的接听电话(请参阅最佳答案),因此,除非有人在Android O之前坚持使用旧的编译API级别,否则可能不再需要这种破解。


它对我没有用。它返回了权限错误。访问未授予应用程序的通知。我正在使用Android L
Jame

2
除了在清单中接受许可外,这还需要一个额外的步骤,即在设置菜单中的某个位置(取决于系统)明确授予用户许可。
headuck

报告这似乎适用于狭义情况:Galaxy A3 2016和棉花糖。我将在一组由于致命异常而无法与输入keyevent方法配合使用的A3设备中进行测试:java.lang.SecurityException:注入到另一个应用程序需要INJECT_EVENTS权限。有问题的设备大约占我用户群的2%,我没有复制它们的异常,但将尝试使用此方法来查看它们是否能够接听电话。幸运的是,我的应用已经在请求显式通知。用于其他目的的访问。
leRobot

经过大量测试后,我很高兴地报告,执行失败的A3 2016设备“ input keyevent”成功与MediaController#dispatchMediaButtonEvent(<hook KeryEvent>))方法一起使用。显然,这仅在用户允许显式通知访问之后才起作用,因此您必须为此添加一个指向Android设置的屏幕,并且您基本上需要用户对此采取额外的操作,如答案中所述。在我的应用中,我们采取了额外的步骤来不断询问用户是否进入该屏幕,但未添加notif。访问
leRobot

这适用于Android Nougat。@notz的解决方案的工作,否则很大,但抱怨Android上7“只有系统可以调度的媒体键事件给全球优先级会话”
彭湃

9

要详细说明@Muzikant的答案,并对其进行一些修改以使其在我的设备上更干净,请尝试input keyevent 79使用KeyEvent.KEYCODE_HEADSETHOOK的常量。非常粗略:

    new Thread(new Runnable() {

        @Override
        public void run() {

            try {

                Runtime.getRuntime().exec( "input keyevent " + KeyEvent.KEYCODE_HEADSETHOOK );
            }
            catch (Throwable t) {

                // do something proper here.
            }
        }
    }).start();

原谅相当糟糕的编码约定,我不太了解Runtime.exec()调用。请注意,我的设备不是root用户,我也不是请求root用户特权。

这种方法的麻烦在于,它仅在某些条件下(对我而言)有效。也就是说,如果我在通话过程中从用户选择的菜单选项中运行上述线程,则通话会很好地应答。如果我从监视传入呼叫状态的接收器运行它,它将被完全忽略。

因此,在我的Nexus 5上,它非常适合用户驱动的应答,并且应适合自定义呼叫屏幕的目的。它不适用于任何类型的自动呼叫控制类型的应用程序。

还需要注意的是所有可能的警告,包括此警告也可能会在一两个更新中停止工作。


input keyevent 79在Sony Xperia 5.0上正常工作。从活动或广播接收者呼叫时有效。
尼古拉斯

0

通过adb命令 如何通过adb接听电话

请记住,Android是Linux,前端是大型JVM。您可以下载命令行应用程序并启动手机,现在您已经拥有一台普通的Linux计算机和命令行,可以执行所有正常操作。运行脚本,您甚至可以ssh(OpenVPN技巧)


0

感谢@notz的答案正在为我在Lolillop上工作。为了使此代码与旧的android SDK一起使用,您可以执行以下代码:

if (Build.VERSION.SDK_INT >= 21) {  
    Intent answerCalintent = new Intent(context, AcceptCallActivity.class);  
    answerCalintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 
                             Intent.FLAG_ACTIVITY_CLEAR_TASK  | 
                             Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    context.startActivity(answerCalintent);  
}  
else {  
  if (telephonyService != null) {  
    try {  
        telephonyService.answerRingingCall();  
    }  
    catch (Exception e) {  
        answerPhoneHeadsethook();  
    }  
  }  
}  

0

自动接听电话后如何打开免提电话。

我用setSpeakerphoneOn解决了上面的问题。我认为在这里值得一提,因为自动接听电话的用例通常也需要免提电话有用。再次感谢在此主题上的每个人,这是一项了不起的工作。

在没有ROOT的Nexus 4上,该版本适用于Android 5.1.1。;)

需要权限:

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

Java代码:

// this means the phone has answered
if(state==TelephonyManager.CALL_STATE_OFFHOOK)
{
    // try and turn on speaker phone
    final Handler mHandler = new Handler();
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            AudioManager audioManager = (AudioManager) localContext.getSystemService(Context.AUDIO_SERVICE);

            // this doesnt work without android.permission.MODIFY_PHONE_STATE
            // audioManager.setMode(AudioManager.MODE_IN_CALL);

            // weirdly this works
            audioManager.setMode(AudioManager.MODE_NORMAL); // this is important
            audioManager.setSpeakerphoneOn(true);

            // note the phone interface won't show speaker phone is enabled
            // but the phone speaker will be on
            // remember to turn it back off when your done ;)
        }
    }, 500); // half a second delay is important or it might fail
}

1
有趣。我实际上是在尝试接听电话并同时打开扬声器,所以这种方法似乎可以解决两个问题:)。我和其他答案中的一些评论有类似的问题:这段代码在哪里?
fangmobile 2013年

-1

以超级用户身份运行以下命令:

input keyevent 5

此处模拟关键事件的更多详细信息

您可以使用我创建的这个基类,以root身份从您的应用程序运行命令。


1
在使用常规用户个人资料进行测试时,这会调出我的通话界面,要求我向左/向右滑动以拒绝/回答或使用快速操作/响应。如果OP正在创建自定义的来电屏幕,除非它在root用户下的行为有所不同,否则这实际上没有任何帮助,我怀疑这对于普通用户而言表现不佳,该呼叫很可能会失败并且不会触发不同的动作。
Valter Jansons,2014年

-2

测试一下:首先添加权限,然后使用killCall()挂断,使用answerCall()接听电话

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


public void killCall() {
    try {
        TelephonyManager telephonyManager =
                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);

        Class classTelephony = Class.forName(telephonyManager.getClass().getName());
        Method methodGetITelephony = classTelephony.getDeclaredMethod("getITelephony");

        methodGetITelephony.setAccessible(true);

        Object telephonyInterface = methodGetITelephony.invoke(telephonyManager);

        Class telephonyInterfaceClass =
                Class.forName(telephonyInterface.getClass().getName());
        Method methodEndCall = telephonyInterfaceClass.getDeclaredMethod("endCall");

        methodEndCall.invoke(telephonyInterface);

    } catch (Exception ex) {
        Log.d(TAG, "PhoneStateReceiver **" + ex.toString());
    }
}

public void answerCall() {
    try {
        Runtime.getRuntime().exec("input keyevent " +
                Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK));

    } catch (IOException e) {
        answerRingingCallWithIntent();
    }
}

public void answerRingingCallWithIntent() {
    try {
        Intent localIntent1 = new Intent(Intent.ACTION_HEADSET_PLUG);
        localIntent1.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
        localIntent1.putExtra("state", 1);
        localIntent1.putExtra("microphone", 1);
        localIntent1.putExtra("name", "Headset");
        getContext().sendOrderedBroadcast(localIntent1, "android.permission.CALL_PRIVILEGED");

        Intent localIntent2 = new Intent(Intent.ACTION_MEDIA_BUTTON);
        KeyEvent localKeyEvent1 = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK);
        localIntent2.putExtra(Intent.EXTRA_KEY_EVENT, localKeyEvent1);
        getContext().sendOrderedBroadcast(localIntent2, "android.permission.CALL_PRIVILEGED");

        Intent localIntent3 = new Intent(Intent.ACTION_MEDIA_BUTTON);
        KeyEvent localKeyEvent2 = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK);
        localIntent3.putExtra(Intent.EXTRA_KEY_EVENT, localKeyEvent2);
        getContext().sendOrderedBroadcast(localIntent3, "android.permission.CALL_PRIVILEGED");

        Intent localIntent4 = new Intent(Intent.ACTION_HEADSET_PLUG);
        localIntent4.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
        localIntent4.putExtra("state", 0);
        localIntent4.putExtra("microphone", 1);
        localIntent4.putExtra("name", "Headset");
        getContext().sendOrderedBroadcast(localIntent4, "android.permission.CALL_PRIVILEGED");
    } catch (Exception e2) {
        e2.printStackTrace();
    }
}

-2

仅供参考,如果您对如何结束Android O上正在进行的通话感兴趣,那么Method 1: TelephonyManager.answerRingingCall()如果您将调用的方法更改为endCall

它只需要 android.permission.CALL_PHONE许可。

这是代码:

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

public void endCall() {
    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("endCall").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);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.