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

时间:2014-11-14 06:59:54

标签: android android-5.0-lollipop phone-call incoming-call

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

10 个答案:

答案 0 :(得分:136)

使用Android 8.0 Oreo进行更新

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

改变了什么?

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

我们如何利用这一变化?

如果您支持较旧的Android版本,则应该check system version at runtime,以便您可以封装此新API调用,同时保持对旧版Android版本的支持。您应该按照requesting permissions at run time在运行时获取该新权限,这是较新的Android版本的标准。

获得许可后,您的应用只需调用TelecomManager's 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)的提交line 1537 on 4.4.3_r1中引入,之后又被重新引入"因为Git树的结构如何,在Lollipop(f1e1e77)的提交line 3138 on 5.0.0_r1中。这意味着除非您只支持使用Lollipop的设备,这可能是基于其目前微小市场份额的糟糕决定,否则如果沿着这条路线走下去,您仍然需要提供后备方法。

我们如何使用它?

由于所讨论的方法对SDK应用程序的使用是隐藏的,因此您需要使用reflection在运行时期间动态检查和使用该方法。如果您不熟悉反射,可以快速阅读What is reflection, and why is it useful?。如果您对此感兴趣,也可以在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。 此权限仅限于部分记录的系统功能,因为不希望第三方接触它(正如您可以从文档中看到的那样)。您可以尝试为其添加<uses-permission>,但这样做没有用,因为此权限的保护级别为 signature | system see line 1201 of core/AndroidManifest on 5.0.0_r1)。

您可以阅读2012年创建的Issue 34785: Update android:protectionLevel documentation,看看我们是否缺少有关特定&#34;管道语法&#34;的详细信息,但是通过试验,看来它必须作为& #39;和&#39;意味着必须满足所有指定的标志才能授予权限。在这个假设下工作,这意味着你必须有你的申请:

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

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

    < / LI>
  2. 使用与框架/ base相同的签名签名,即系统,即ROM。

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

  3. 此外,此行为似乎与Issue 34792: Android Jelly Bean / 4.1: android.permission.READ_LOGS no longer works相关,后者使用相同的保护级别以及未记录的开发标记。

    使用TelephonyManager听起来不错,但除非您获得在实践中不容易做到的适当许可,否则无效。

    如何以其他方式使用TelephonyManager?

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


    方法2:服务呼叫SERVICE CODE

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

    无法与TelephonyManager交互,也可以通过service可执行文件与服务进行交互。

    这是如何工作的?

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

    • 我们要使用的服务名称电话

      可以通过运行service list

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

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

    使用service call phone 5

    命令生成此结果

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

    爪哇

    以下代码是一个粗略的实现,用作概念证明。如果您确实希望继续使用此方法,则可能需要查看guidelines for problem-free su usage并可能切换到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,但我无法获得该路线的运气。

    这有多稳定?

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


    原始方法:耳机键码意图

    您必须安顿下来的时间。

    以下部分受this answer 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是禁止的或只是没有文档。你应该谨慎行事。

答案 1 :(得分:33)

完全可行的解决方案基于@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);

答案 2 :(得分:13)

以下是另一种适用于我的方法。它使用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(请参阅最佳答案)之后可以接听正式的电话接听电话时,可能不再需要这种黑客攻击,除非在Android O之前使用旧的编译API级别。

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

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

答案 4 :(得分:0)

通过adb命令 How to pick up a call by adb

请记住,Android是Linux,前端有大量JVM。你可以下载一个命令行应用程序并根电话,现在你有一个普通的Linux计算机和命令行,可以执行所有正常操作。运行脚本,你甚至可以ssh到它(OpenVPN技巧)

答案 5 :(得分: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();  
    }  
  }  
}  

答案 6 :(得分:0)

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

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

这适用于我的Nexus 4上没有ROOT的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
}

答案 7 :(得分:-1)

以root身份运行以下命令:

input keyevent 5

有关模拟关键字事件here的详细信息。

您可以使用this base class I created以root用户身份运行命令。

答案 8 :(得分:-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();
    }
}

答案 9 :(得分:-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);
    }
}