91

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

10 回答 10

164

使用 Android 8.0 奥利奥更新

尽管这个问题最初是针对 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();

方法一:TelephonyManager.answerRingingCall()

当您可以无限控制设备时。

这是什么?

TelephonyManager.answerRingingCall() 是一种隐藏的内部方法。它作为 ITelephony.answerRingingCall() 的桥梁,已在互联网上进行了讨论,并且一开始似乎很有希望。它在4.4.2_r1上不可用,因为它仅在Android 4.4 KitKat的提交83da75d中引入(4.4.3_r1上的第 1537 行),后来在Lollipop的提交f1e1e77中“重新引入” (5.0.0_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。此权限仅在系统的部分记录功能范围内,因为不希望第 3 方接触它(正如您从文档中看到的那样)。您可以尝试为其添加一个<uses-permission>,但这不会有任何好处,因为此权限的保护级别是签名|系统请参阅 5.0.0_r1 上 core/AndroidManifest 的第 1201 行)。

您可以阅读问题 34785:更新 android:protectionLevel 文档,该文档创建于 2012 年,以了解我们缺少有关特定“管道语法”的详细信息,但通过试验,它似乎必须充当“与”,这意味着所有必须满足指定的标志才能授予权限。在这种假设下工作,这意味着您必须拥有您的应用程序:

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

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

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

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

此外,此行为似乎与问题 34792:Android Jelly Bean / 4.1:android.permission.READ_LOGS 不再有效,它使用相同的保护级别以及未记录的开发标志。

使用 TelephonyManager 听起来不错,但除非您获得适当的许可,否则将无法正常工作,这在实践中并不容易做到。

以其他方式使用 TelephonyManager 怎么样?

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


方法二:服务调用SERVICE CODE

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

在无法与 TelephonyManager 交互的情况下,也有可能通过service可执行文件与服务交互。

这是如何运作的?

这相当简单,但是关于这条路线的文档比其他路线还要少。我们确信可执行文件有两个参数——服务名称和代码。

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

    这可以通过运行看到service list

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

    看起来它现在已经基于IBinder.FIRST_CALL_TRANSACTION + 5 用于许多版本(从1.5_r44.4.4_r1),但是在本地测试期间,代码 5 可以接听来电。由于 Lollio 是一次大规模的更新,因此这里的内部结构也发生了变化,这是可以理解的。

这导致命令为service call phone 5

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

爪哇

以下代码是作为概念证明的粗略实现。如果你真的想继续使用这个方法,你可能想查看无问题的 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,但我无法通过该路线获得任何运气。

这有多稳定?

我很高兴你问。由于没有记录,这可能会跨越各种版本,如上面看似的代码差异所示。服务名称可能应该在各种构建中保持电话号码,但据我们所知,代码值可能会在同一版本的多个构建中发生变化(例如,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;博士

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

在 Android 8.0 Oreo 之前没有公共 API。内部 API 是禁止使用的,或者根本没有文档。您应该谨慎行事。

于 2014-11-23T00:47:59.790 回答
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);
于 2015-04-15T13:08:11.813 回答
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 O中,需要先模拟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 以来就可以使用官方接听电话(请参阅最佳答案),因此可能不再需要这种 hack,除非人们在 Android O 之前被困在旧的编译 API 级别。

于 2015-05-14T08:17:00.000 回答
9

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

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

同样值得注意的是所有可能的警告,包括这也可能会在一两次更新中停止工作。

于 2014-11-25T21:52:56.880 回答
1

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

请记住,Android 是在前端带有大量 JVM 的 Linux。你可以下载一个命令行应用程序并 root 手机,现在你有一台普通的 Linux 计算机和命令行,可以做所有正常的事情。运行脚本,你甚至可以通过 ssh 访问它(OpenVPN 技巧)

于 2014-11-22T04:11:15.587 回答
0

谢谢@notz,答案是在棒棒糖上为我工作。为了使此代码与旧的 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();  
    }  
  }  
}  
于 2015-05-11T15:51:46.980 回答
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
}
于 2016-03-18T10:46:14.820 回答
-2

Run the following command as root:

input keyevent 5

More details on simulating keyevents here.

You can use this base class I created to run commands as root from your app.

于 2014-11-23T11:17:17.557 回答
-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();
    }
}
于 2016-04-21T07:16:51.317 回答
-2

仅供参考,如果您对如何在 Android O 上结束正在进行的呼叫感兴趣,Method 1: TelephonyManager.answerRingingCall()如果您将调用的方法更改为,则 Valter 的工作原理是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);
    }
}
于 2017-08-24T18:10:31.403 回答