在Android 5.0(棒棒糖)中,如何通过编程的方式自动接听来电?

95

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

2
有人吗?我也对这个感兴趣!尝试了很多方法,但它们都没有起作用 :/ - Arthur
1
@nobalG 他是在程序上说的。 - dsharew
@maveroid你是否需要澄清解决方案是否可以使用root权限?虽然我不能依赖设备被root,但你的情况可能不同,并且会影响答案。 - Riley C
1
@maveroid,你有没有想到适用于Android 5.0的解决方法? - arthursfreire
@arthursfreire 还没有。 - maveroid
显示剩余4条评论
10个回答

170

使用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();

方法一: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的具体细节。

那么在代码中看起来怎样?

// 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>,但那样没有任何作用,因为这个权限的保护级别是signature|system请参见5.0.0_r1版本的core/AndroidManifest第1201行)。

你可以阅读2012年创建的Issue 34785: 更新android:protectionLevel文档,以查看我们缺少有关特定“管道语法”的详细信息,但从实验中可以看出,它必须作为“AND”运行,这意味着所有指定的标志都必须满足才能授予权限。基于这个假设,这意味着你的应用程序必须:
  1. 安装为系统应用程序。

    这应该是可以的,可以通过要求用户在恢复模式下使用ZIP进行安装,例如在root或安装Google应用程序的自定义ROM上,这些应用程序没有被预先打包。

  2. 使用与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可执行文件与服务进行交互。

这是如何工作的?

这相当简单,但比其他路线的文档还要少。我们确定可执行文件需要传入两个参数 - 服务名称和代码。

  • 我们想要使用的服务名称phone

    可以通过运行service list来查看。

  • 我们想要使用的代码似乎曾经是6,但现在似乎变成了5

    看起来它已经基于IBinder.FIRST_CALL_TRANSACTION + 5很多版本了(从1.5_r44.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();
}

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

这真的需要root权限吗?

很遗憾,似乎是这样。你可以尝试在其上使用Runtime.exec,但我并没有成功。

这有多稳定?

很高兴你问了这个问题。由于没有文档记录,这可能会因版本不同而破坏,正如上面所示的代码差异。服务名称应该保持为phone,但我们都知道,代码值可以在同一版本的多个构建中(例如OEM皮肤的内部修改)发生变化,进而破坏所使用的方法。因此,值得一提的是,测试是在Nexus 4(mako/occam)上进行的。个人建议你不要使用这种方法,但由于我无法找到更稳定的方法,我认为这是最好的尝试。


原始方法:耳机按键代码意图

在你不得不妥协的时候。

以下部分受到Riley Cthis 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) {
            // 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();

简述

Android 8.0 Oreo及以上版本有一个很好的公共API。

Android 8.0 Oreo之前没有公共API。内部API是禁止访问的,或者没有文档。您应该小心谨慎。


1
对于那些发现原始方法似乎不起作用的情况(例如Lollipop存在问题)的人们,我有好消息和坏消息:在我的代码中,我成功地使用FULL_WAKE_LOCK让ACTION_UP完美运行了100%。 它将无法与PARTIAL_WAKE_LOCK一起使用。关于为什么会这样的原因绝对没有任何文档说明。当我更广泛地测试实验代码时,我将在未来的答案中详细说明这一点。坏消息是,FULL_WAKE_LOCK已被弃用,因此这只是一个修复,只要Google将其保留在API中就能持续使用。 - leRobot
谢谢……我已经成功地测试了几乎所有的设备,包括4.0、4.1、4.2、4.3、4.4以及5.0、5.1。所有的设备都能正常工作。 - GvSharma
测试在5.0.2摩托罗拉Moto G上可以工作,但在5.0.1三星Galaxy S4上测试失败...这是怎么回事?有什么猜测吗? - David
测试通过 5.1.1。这个可以工作:Runtime.getRuntime().exec("input keyevent " + Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK)); - rudakovsky
1
原始答案中的catch在许多情况下都没有被调用。我发现最好先调用exec,然后立即调用按钮上下。 - Warpzit
显示剩余37条评论

37

完全可用的解决方案基于@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
我应该在“最后调用魔法”下面添加代码。这对Android 6.0有效吗? - Akshay Shah
1
我也可以拒绝一个电话吗?你能否更新答案以显示这一点? - Amanni
@leRobot 这个答案检查了是否为HTC设备来广播HeadsetConnected,那么如何检查它是否为三星A3 2016设备呢?顺便说一句,这真的是一个很好的答案,我的应用程序可以在屏幕锁定时接听电话。 - eepty
很好的答案。需要检查Android 7并切换到@headuck的解决方案。在Nougat上,它会抱怨“只有系统才能将媒体键事件分派到全局优先级会话”。 - P. B.
在Sony Z3 Android 5.0.2上运行良好,将在更新的Android版本上进行检查。 - SHAHS
显示剩余7条评论

14
以下是我使用的替代方法。它使用MediaController API直接将按键事件发送到电信服务器。这需要应用程序具有BIND_NOTIFICATION_LISTENER_SERVICE权限,并且用户明确授予通知访问权限:

以下是我使用的替代方法。它使用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 O中,需要在ACTION_UP之前模拟ACTION_DOWN,否则上述操作将无效。即需要执行以下操作:

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 级别,否则可能不再需要这个 hack。


它对我没用。它返回了权限错误。该应用程序未被授予访问通知的权限。我正在使用Android L。 - Jame
2
这需要用户在设置菜单中的某个位置明确授予权限,除了在清单中接受权限之外。 - headuck
这在Android Nougat上有效。@notz的解决方案在其他情况下非常好,但在Android 7上会抱怨“只有系统才能将媒体键事件分派到全局优先级会话”。 - P. B.
最终,这个解决方案只解决了我的问题。首先我得到了“缺少控制媒体权限”的异常,然后我从以下解决方案中给出了权限: http://stackoverflow.com/a/27993447/1404798 在Moto G3(Android 6)上运行良好。谢谢@headuck - Thirumalvalavan
拒绝来电,请使用以下代码: 先接听电话,然后使用相同的代码结束通话: 所以只需模拟两次触摸而不是一次! m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK)); m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK)); m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK)); - Mehrdad
显示剩余4条评论

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上,它适用于由用户驱动的应答,并且适合自定义呼叫屏幕的目的。但它不适用于任何类型的自动化呼叫控制应用程序。

还要注意所有可能的警告,包括这个方法在未来的一两个更新中可能会停止工作。


在Sony Xperia 5.0上,“input keyevent 79”命令可以正常工作。无论是从活动中调用还是从广播接收器中调用,都能正常工作。 - nicolas

1

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

请记住,Android是具有大规模JVM的Linux。您可以下载命令行应用程序并对手机进行root,现在您拥有了一个普通的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解决了上述问题。我认为这值得在这里发布,因为自动接听电话的用例通常也需要扬声器才能发挥作用。再次感谢本主题中的每个人,做得很棒。

这对我在我的Nexus 4上运行的Android 5.1.1上有效,无需ROOT。;)

所需权限:

<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

-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上结束正在进行的通话感兴趣,Valter的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);
    }
}

-2

以 root 身份运行以下命令:

input keyevent 5

有关模拟按键事件的更多详细信息在这里

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


1
在使用普通用户配置文件进行测试时,这为我带来了呼叫中的UI,要求我向左/向右滑动以拒绝/接听或使用快速操作/响应。如果OP正在创建自定义的来电屏幕,则除非在root下表现不同,否则这并没有什么帮助,我怀疑如果它对普通用户没有良好的表现,那么呼叫可能只会失败而不会触发其他操作。 - Valter Jansons

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接