94

As I am trying to create a custom screen for incoming calls I am trying to programatically answer an incoming call. I am using the following code but it is not working in 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");
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
maveroid
  • 1,840
  • 1
  • 20
  • 20

10 Answers10

168

Update with Android 8.0 Oreo

Even though the question was originally asked for Android L support, people still seem to be hitting this question and answer, so it is worth describing the improvements introduced in Android 8.0 Oreo. The backward compatible methods are still described below.

What changed?

Starting with Android 8.0 Oreo, the PHONE permission group also contains the ANSWER_PHONE_CALLS permission. As the name of permission suggests, holding it allows your app to programmatically accept incoming calls through a proper API call without any hacking around the system using reflection or simulating the user.

How do we utilize this change?

You should check system version at runtime if you are supporting older Android versions so that you can encapsulate this new API call while maintaining support for those older Android versions. You should follow requesting permissions at run time to obtain that new permission during run-time, as is standard on the newer Android versions.

After having obtained the permission, your app just has to simply call the TelecomManager's acceptRingingCall method. A basic invocation looks as follows then:

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

Method 1: TelephonyManager.answerRingingCall()

For when you have unlimited control over the device.

What is this?

There is TelephonyManager.answerRingingCall() which is a hidden, internal method. It works as a bridge for ITelephony.answerRingingCall() which has been discussed on the interwebs and seems promising at the start. It is not available on 4.4.2_r1 as it was introduced only in commit 83da75d for Android 4.4 KitKat (line 1537 on 4.4.3_r1) and later "reintroduced" in commit f1e1e77 for Lollipop (line 3138 on 5.0.0_r1) due to how the Git tree was structured. This means that unless you only support devices with Lollipop, which is probably a bad decision based on the tiny market share of it as of right now, you still need to provide fallback methods if going down this route.

How would we use this?

As the method in question is hidden from the SDK applications use, you need to use reflection to dynamically examine and use the method during runtime. If you are not familiar with reflection, you can quickly read What is reflection, and why is it useful?. You can also dig deeper into the specifics at Trail: The Reflection API if you are interested in doing so.

And how does that look in code?

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

This is too good to be true!

Actually, there is one slight problem. This method should be fully functional, but the security manager wants callers to hold android.permission.MODIFY_PHONE_STATE. This permission is in the realm of only partially documented features of the system as 3rd parties are not expected to touch it (as you can see from the documentation for it). You can try adding a <uses-permission> for it but that will do no good because the protection level for this permission is signature|system (see line 1201 of core/AndroidManifest on 5.0.0_r1).

You can read Issue 34785: Update android:protectionLevel documentation which was created back in 2012 to see that we are missing details about the specific "pipe syntax", but from experimenting around, it appears it must function as an 'AND' meaning all the specified flags have to be fulfilled for the permission to be granted. Working under that assumption, it would mean you must have your application:

  1. Installed as a system application.

    This should be fine and could be accomplished by asking the users to install using a ZIP in recovery, such as when rooting or installing Google apps on custom ROMs that don't have them already packaged.

  2. Signed with the same signature as frameworks/base aka the system, aka the ROM.

    This is where the problems pop up. To do this, you need to have your hands on the keys used for signing frameworks/base. You would not only have to get access to Google's keys for Nexus factory images, but you would also have to get access to all other OEMs' and ROM developers' keys. This does not seem plausible so you can have your application signed with the system keys by either making a custom ROM and asking your users to switch to it (which might be hard) or by finding an exploit with which the permission protection level can be bypassed (which might be hard as well).

Additionally, this behavior appears to be related to Issue 34792: Android Jelly Bean / 4.1: android.permission.READ_LOGS no longer works which utilizes the same protection level along with an undocumented development flag as well.

Working with the TelephonyManager sounds good, but will not work unless you get the appropriate permission which is not that easy to do in practice.

What about using TelephonyManager in other ways?

Sadly, it appears to require you to hold the android.permission.MODIFY_PHONE_STATE to use the cool tools which in turn means you are going to have a hard time getting access to those methods.


Method 2: service call SERVICE CODE

For when you can test that the build running on the device will work with the specified code.

Without being able to interact with the TelephonyManager, there is also the possibility of interacting with the service through the service executable.

How does this work?

It is fairly simple, but there is even less documentation about this route than others. We know for sure the executable takes in two arguments - the service name and the code.

  • The service name we want to use is phone.

    This can be seen by running service list.

  • The code we want to use appears to have been 6 but seems to now be 5.

    It looks like it has been based on IBinder.FIRST_CALL_TRANSACTION + 5 for many versions now (from 1.5_r4 to 4.4.4_r1) but during local testing the code 5 worked to answer an incoming call. As Lollipo is a massive update all around, it is understandable internals changed here as well.

This results with a command of service call phone 5.

How do we utilize this programmatically?

Java

The following code is a rough implementation made to function as a proof of concept. If you actually want to go ahead and use this method, you probably want to check out guidelines for problem-free su usage and possibly switch to the more fully developed libsuperuser by 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();
}

Manifest

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

Does this really require root access?

Sadly, it seems so. You can try using Runtime.exec on it, but I was not able to get any luck with that route.

How stable is this?

I'm glad you asked. Due to not being documented, this can break across various versions, as illustrated by the seeming code difference above. The service name should probably stay phone across various builds, but for all we know, the code value can change across multiple builds of the same version (internal modifications by, say, the OEM's skin) in turn breaking the method used. It is therefore worth mentioning the testing took place on a Nexus 4 (mako/occam). I would personally advise you against using this method, but as I am not able to find a more stable method, I believe this is the best shot.


Original method: Headset keycode intents

For times when you have to settle.

The following section was strongly influenced by this answer by Riley C.

The simulated headset intent method as posted in the original question seems to be broadcast just as one would expect, but it doesn't appear to accomplish the goal of answering the call. While there appears to be code in place that should handle those intents, they simply aren't being cared about, which has to mean there must be some kind of new countermeasures in place against this method. The log doesn't show anything of interest either and I don't personally believe digging through the Android source for this will be worthwhile just due to the possibility of Google introducing a slight change that easily breaks the method used anyway.

Is there anything we can do right now?

The behavior can be consistently reproduced using the input executable. It takes in a keycode argument, for which we simply pass in KeyEvent.KEYCODE_HEADSETHOOK. The method doesn't even require root access making it suitable for common use cases in the general public, but there is a small drawback in the method - the headset button press event cannot be specified to require a permission, meaning it works like a real button press and bubbles up through the whole chain, which in turn means you have to be cautious about when to simulate the button press as it could, for example, trigger the music player to start playback if nobody else of higher priority is ready to handle the event.

Code?

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

There is a nice public API for Android 8.0 Oreo and later.

There is no public API prior to Android 8.0 Oreo. The internal APIs are off-limits or simply without documentation. You should proceed with caution.

Warpzit
  • 27,966
  • 19
  • 103
  • 155
Valter Jansons
  • 3,904
  • 1
  • 22
  • 25
  • With regards to the headset keycode intents, have you checked out the Google source [here](https://android.googlesource.com/platform/packages/services/Telecomm/+/master/src/com/android/server/telecom/HeadsetMediaButton.java) for any reason why they would stop being acted upon? The funny thing is that calls can still be easily declined with these intents (just emulate a long-press), but nothing works to answer. I've yet to find an explicit permission check or other potential block and am hoping a second set of eyes might uncover something. – Riley C Nov 25 '14 at 16:43
  • Was a bit busy hence the delay - will try to spend some time figuring this out. After a quick look, it seems CallsManager constructs HeadsetMediaButton. [The session callback](https://android.googlesource.com/platform/packages/services/Telecomm/+/android-5.0.0_r1/src/com/android/server/telecom/HeadsetMediaButton.java#39) there should take care of calling handleHeadsetHook(KeyEvent) upon callbacks from MediaSessionManager. All code seems to match up... but I wonder, could someone remove the KeyEvent.ACTION_DOWN intent to test? (That is, only fire the KeyEvent.ACTION_UP one single time.) – Valter Jansons Nov 25 '14 at 17:53
  • Actually, HeadsetMediaButton works with the MediaSession and does not interact with the MediaSessionManager _directly_... – Valter Jansons Nov 25 '14 at 18:07
  • @ValterStrods sir can you please give me hint for accepting call in lollipop ( NEXUS 5 )? i have use this two intent also but it fails to accept call – Vaishali Sutariya Feb 28 '15 at 12:30
  • @Vaishali If you are having trouble making the `input` call, I would suggest you to log the I/O exception that happens. That might contain something of interest as I remember the call working fairly well when testing back in the day, with the exception of it being passed on to other applications as well (such as media players) as was mentioned in the answer. – Valter Jansons Feb 28 '15 at 12:35
  • @ValterStrods but sir i have use this both intents but still m not able to take call , and m not facing any trouble to do make call – Vaishali Sutariya Feb 28 '15 at 12:39
  • @Vaishali I am sorry, but I do not seem to understand your issue entirely so I would suggest you to open a new question describing your problem in full detail. – Valter Jansons Feb 28 '15 at 13:11
  • @ValterStrods yeah i have put it also on http://stackoverflow.com/questions/28781872/cant-able-to-answer-incoming-call-in-android-lollipop-5-0 – Vaishali Sutariya Mar 02 '15 at 05:04
  • @Vaishali Oh, you were asking how to make the code in the question work? This whole answer explains that then. I thought one of the three described methods was not working for you. The easiest and most portable way to make it work will be the "Original method" section in this answer. It includes a code fragment showing how it works as well. – Valter Jansons Mar 02 '15 at 05:49
  • 1
    For those of you that managed to find a situation where the Original Method doesn't seem work (e.g. Lollipop has issues), I have good and bad news: I have managed to get the ACTION_UP working 100%, in my code, with a FULL_WAKE_LOCK. It will not work with a PARTIAL_WAKE_LOCK. There is absolutely no documentation whatsoever about the why's of this. I will detail this in a future answer when I test my experiment code more extensively. The bad news is, of course, FULL_WAKE_LOCK is deprecated, so this is a fix that will only last as long as Google keeps it in the API. – leRobot Apr 09 '15 at 12:23
  • @user2282338 That does not seem like a discussion suited for this answer's comment feed, but I would just guess you acquire a wake lock and then the call works. You don't acquire a full wake lock, call ain't not working. – Valter Jansons Apr 11 '15 at 18:31
  • @user2282338 What are you referring to with "these methods" and what is your setup? If we are talking stock OEM 5.1, methods 1 and 2 are pretty much dead ends as described in the method descriptions. Method 3 should work fine for all devices as long as you make sure you don't make the call when no call is coming in as that will make the event bubble up. I must confess I have not tested this on 5.1 as 5.0 was the relevant version at the time of writing this answer and what the question asked for, but I do not think there should be any reason for it to fail even on 5.1. – Valter Jansons Apr 11 '15 at 18:56
  • The original method works in 4.03 and 4.4, I checked. In 5.1 (Nexux 5) it does not work. – gc986 Apr 11 '15 at 19:02
  • @user2282338 "Original method" as the one OP provided or my modification of it? – Valter Jansons Apr 11 '15 at 19:05
  • Very sorry but all modifications tested by me do not work in 5.1 – gc986 Apr 11 '15 at 19:11
  • @user2282338 Could you try running `input keyevent 79` on the device manually? – Valter Jansons Apr 11 '15 at 19:22
  • You are right! On normal activity at work. I was wrong. But nevertheless the method in 5.1 returns the Context has changed (BroadcastReceiverю.onReceive), 4.03-4.4 everything works correctly. – gc986 Apr 11 '15 at 21:38
  • Thanks....i have successfully tested it for almost 4.0, 4.1, 4.2, 4.3, 4.4 also 5.0, 5.1,,,,,Fine all devices are working – GvSharma Jul 22 '15 at 05:46
  • Tested on 5.0.2 Motorola Moto G works, tested on 5.0.1 Samsung Galaxy S4 doesn't... WTF!? Any guess why this is happening? – David Aug 26 '15 at 10:43
  • Tested on 5.1.1. This one works: Runtime.getRuntime().exec("input keyevent " + Integer.toString(KeyEvent.KEYCODE_HEADSETHOOK)); – rudakovsky Oct 28 '15 at 14:29
  • KEYCODE_HEADSETHOOK method takes time of few seconds before call is actually answered. Any solution to this? – devendrant Nov 03 '15 at 05:59
  • You my friend a LEGEND! That is one of the best posts iv ever read. The new Thread(new Runnable() { solution is working for me UNROOTED on 5.1.1 on my Nexus 4. AWESOME. :) – Madhava Jay Mar 17 '16 at 22:52
  • Anyone know how to enable Speaker Phone as well? – Madhava Jay Mar 17 '16 at 22:54
  • 1
    The catch from the original answer is not called in many cases. I've found it better to just call exec first and then call the button up down anyway right after. – Warpzit Apr 16 '16 at 19:25
  • @Warpzit I would believe it is possible that the input answers the call and the headset hook gets triggered a second time, which can cause undefined behaviour (unexpected, weird behaviour). If it works for you, go for it.. but be warned. – Valter Jansons Apr 16 '16 at 20:05
  • @ValterJansons We'll do some extensive testing but so far it seems like the only way to get it to work properly. The suggested approach won't work on most devices because the catch never is reached... – Warpzit Apr 17 '16 at 19:47
  • @ValterJansons if you're having trouble with some devices you should look at this answer: http://stackoverflow.com/a/29651130/1103974 . It seems to have fixed the issue on some devices I had failing recently, such as the Samsung A3 2016 (5.1.1). The headset plug broadcast is key for that one. I also reverse engineered some market apps that work and they do the same thing (I kno I kno... very unprofessional) – leRobot May 18 '16 at 09:23
  • having dealt with this issue for ~2y for a complex app where this is critical, I can share some insights: full_wake_lock/separate thread/activity (if you're calling this in a service) seem important for specific APIs and devices. Some "like" headset down only, some need down+up. Some need headset plug before that, some need it after down+up too (unplug). Order also matters: I had to set conditionals for devices that will accept 2 methods as they would answer->hang. Timing also sux - phone state receivers to stop using alternatives is no solution as answering will take up to 3s on some phones – leRobot May 18 '16 at 09:38
  • and last but not least, phones with a secure lock screen are a BIG problem. My app has a window on top of the WindowManager with FLAG_SHOW_WHEN_LOCKED set, and a button to trigger answer. Some samsung phones are a real pest to get to accept call consistently with secure lockscreen on, even with this flag - sometimes they insta-accept, sometimes they only accept if you spam that button. Pragmatically I believe it's all Samsung/Knox's fault on those, but I digress... – leRobot May 18 '16 at 09:40
  • @leRobot A lot of "ifs". Thanks for the answer. I don't feel like downgrading to Lollipop so I cant test your suggestions out to feel good about updating the answer. During my testing, the simple HEADSETHOOK worked without any plugs/unplugs being sent or any other trickery being done along with the fact that a wakelock makes little sense in my head for this - the device should already be awake when the call is shown on screen (which is right away). – Valter Jansons May 18 '16 at 09:47
  • TelephonyManager.answerRingingCall() is now open for Android O version – Jemo Mgebrishvili Mar 22 '17 at 05:38
  • One thing that is causing issues for my app is that even if you don't target API 26 (O) the `Headset keycode intents` method will no longer work on phones running O. That's really annoying and forced me to increase my target to 26 just so I can keep this functionality working on O. Lastly, a word of caution to all who plan on using this new API. This new phone permission is in the PHONE group but if the user already gave you the phone group permission doesn't mean you will get this new one out of the box. You have to find a way to explain that they have to give that permission again. Not great – Francois Dermu Aug 23 '17 at 16:46
  • Shouldn't the Android Oreo update be TelecomManager.acceptRingingCall() ? – brandall Sep 26 '17 at 21:41
  • @ValterJansons under 'How do we utilize this change' you are referencing TelephonyManager answerRingingCall() The Oreo update is TelecomManager acceptRingingCall() ? Unless I misunderstood you! – brandall Sep 27 '17 at 08:49
  • I believe the Oreo advice still stands as when Oreo dropped because I do not see anything having changed for Android P by looking at the currently announced Behavior Changes and the new Features and APIs. – Valter Jansons Mar 27 '18 at 15:01
  • Anybody know how to kill a call pragmatically in android I am using a source code that work only prior to Oreo but not on oreo. – Ali Akram May 08 '18 at 06:32
  • @AliAkram That's totally outside the scope of the asked question here - feel free to open a new question if you are struggling with that. – Valter Jansons May 08 '18 at 09:55
  • @Valter Jansons I know but Somehow related to handling and dealing with phone call, If you have answer to my question kindly see here i posted a new question. https://stackoverflow.com/questions/50227490/kill-a-phone-call-programatically-in-oreo – Ali Akram May 08 '18 at 10:03
  • @ValterJansons the `tm.acceptRingingCall()` wotked fine with me in `Android O`, but I could not find how to `reject/cancel` incoming call instead of accepting it. – Hasan A Yousef May 18 '18 at 20:37
  • Quite helpful. Good answer +1. – Miron May 29 '18 at 15:53
  • Btw, maybe someone might find this useful for older devices ;-) : https://github.com/anggrayudi/android-hidden-api. To the poster, THANK YOU for the post!!! – Edw590 Sep 30 '20 at 02:23
  • A question about this though. I'm doing the Method 1 on Lollipop and the method exists here. I have the supposedly correct permissions and I'm running the app as a system app and with root permissions. But this freezes the entire app. I can answer or end the call, but then the app freezes. Would you know a reason? EDIT: Method 2 freezes the app too. I wonder why. I have the permissions here or this wouldn't answer the call. Root and system app too. Why.... – Edw590 Sep 30 '20 at 19:38
37

The fully working solution is based on @Valter Strods code.

To get it working, you have to display an (invisible) activity on lock screen where the code is executed.

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>

Call Accept 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

<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>

Finally call the magic!

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);
notz
  • 2,458
  • 2
  • 15
  • 11
  • 1
    where m i suppoz to add the code under "Finally call the magic". Will it work for Android 6.0 – Akshay Shah May 06 '16 at 08:43
  • I came here to say that broadcastHeadsetConnected(boolean connected) is what solved a problem in a Samsung A3 2016 device. Without that, a very similar method (using separate, transparent activity, and threads for invokes and whatnot) was working fully for about 20 tested devices, then this A3 came along and forced me to re-check this question for new answers. After comparing with my code, that was the significant difference! – leRobot May 06 '16 at 17:24
  • 1
    How can I reject a call as well? Can you update the answer to show this? – Amanni Jun 16 '16 at 17:36
  • @leRobot This answer check whether it is a HTC device to broadcastHeadsetConnected, how can you check whether it is a Samsung A3 2016 device? By the way this is really a good answer, my app can answer the phone call even the screen is locked. – eepty Jul 07 '16 at 18:33
  • @eepty You can use the official device reference for Build data. (https://support.google.com/googleplay/answer/1727131?hl=en). I use Build.MODEL.startsWith("SM-A310") for the A3 2016. BUT! I can confirm the A3 2016 doesn't support headset connected broadcasts! What actually solved my issue was changing the order so that the Runtime.getRuntime().exec(... triggers first for these devices. It seems to work every time for that device and does not fall back to the exception. – leRobot Jul 08 '16 at 09:31
  • I can't get it to answer the call when the screen is locked. Any solution to this? – Amanni Jul 25 '16 at 15:11
  • @notz: It is very good solution. It worked. But i cannot press endcall button in the GUI of phone when using above method, although the calling is accepted. I am using S4, Android L – Jame Sep 24 '16 at 10:12
  • @Amanni very late answer, but this might be fixable by using WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED. You have to check if KeyguardManager.inKeyguardRestrictedInputMode() is true, then add those flags to the activity window (Activity.getWindow). FYI: I am also using WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD and WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON, for good measure!! – leRobot Jan 11 '17 at 11:17
  • This method has worked for me on most of my devices. However it does not work on many Samsung devices, including S4, S7, and SM-J700M. Any idea how to make it work with these devices? – Janusz Kacalak Feb 02 '17 at 19:27
  • Great answer. Need to check for Android 7 and switch to the solution of @headuck. It complains "Only the system can dispatch media key event to the global priority session" on Nougat. – P. B. Apr 02 '17 at 21:42
  • Worked well on Sony Z3 android 5.0.2, Will check on newer android versions. – SHAHS Jul 21 '17 at 13:11
  • About `isWiredHeadsetOn` , it is marked as deprecated. What should be used instead? Is this a good solution : https://stackoverflow.com/a/45726363/878126 ? – android developer Jul 25 '18 at 11:45
14

The following is an alternative approach which worked for me. It sends the key event to the telecom server directly using the MediaController API. This requires that the app has BIND_NOTIFICATION_LISTENER_SERVICE permission and is given explicit grant of Notification access from the user:

@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 in the above code could be just an empty class.

import android.service.notification.NotificationListenerService;

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

With the corresponding section in the manifest:

    <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>

Since the target of the event is explicit this should probably avoid any side effect of triggering the media player.

Note: the telecom server may not be immediately active after the ringing event. For this to work reliably, it may be useful for the app to implement MediaSessionManager.OnActiveSessionsChangedListener to monitor when the telecom server becomes active, before sending the event.

Update:

In Android O, one need to simulate ACTION_DOWN before ACTION_UP, otherwise the above has no effect. i.e. the following is needed:

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

But since an official call for answering call is available since Android O (see top answer), there may no longer be a need for this hack, unless one is stuck with an old compile API level before Android O.

headuck
  • 2,763
  • 16
  • 19
  • It did not work for me. It returned Permission error. Access to notification not granted to the app. I am using Android L – Jame Sep 24 '16 at 09:19
  • 2
    This requires an additional step of explicitly granting the permission by the user, somewhere in the settings menu depending on the system, besides accepting the permission in the manifest. – headuck Sep 25 '16 at 23:48
  • reporting this seems to work in a narrow case: Galaxy A3 2016 with Marshmallow. I will be testing this in a group of A3 devices that are not working with the input keyevent method due to a FATAL EXCEPTION: java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission. Offending devices are about 2% of my user-base and I am not replicating their exception, but will be attempting this method to see if they manage to pick up the call. Fortunately my app is already requesting explicit notif. access for other purposes. – leRobot Jan 11 '17 at 12:26
  • after extensive testing, I am glad to report that A3 2016 devices that were failing with exec "input keyevent" managed to work with the MediaController#dispatchMediaButtonEvent()) method. this obviously only works after the user allows explicit Notification Access, so you'll have to add a screen directing to Android Settings for that, and you basically need the user to take extra action for this, as detailed on the answer. In my app we've taken extra steps to keep asking this if the user goes to that screen but doesn't add notif. access – leRobot Jan 23 '17 at 14:30
  • This works on Android Nougat. The solution of @notz works great otherwise, but complains "Only the system can dispatch media key event to the global priority session" on Android 7. – P. B. Apr 02 '17 at 21:41
  • Finally this solution only solved my issue. First I got "Missing permission to control media." exception, Than I give the permission from below solution: http://stackoverflow.com/a/27993447/1404798 It is working in Moto G3 (Android 6). Thanks @headuck – Thirumalvalavan Apr 07 '17 at 13:11
  • Where is sendHeadsetHookLollipop() called? – SavageCore May 04 '17 at 17:15
  • It works for answering calls.But just as you said,an official call for answering call is available since Android O.Actually what matters is how to reject the incomming call. – EricZhao Jul 04 '18 at 01:22
  • for rejecting the call , use this: accept the call then with the same code you can end the call: so just simulate two touches instead of one! 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 Jan 21 '19 at 03:58
9

To elaborate a tiny bit on the answer by @Muzikant, and to modify it a bit to work a bit cleaner on my device, try input keyevent 79, the constant for KeyEvent.KEYCODE_HEADSETHOOK. Very roughly:

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

Forgive the fairly bad coding conventions, I'm not too well-versed in Runtime.exec() calls. Note that my device is not rooted, nor am I requesting root privileges.

The trouble with this approach is that it only works under certain conditions (for me). That is, if I run the above thread from a menu option that the user selects while a call is ringing, the call answers just fine. If I run it from a receiver that monitors incoming call status, it gets totally ignored.

So on my Nexus 5 it works well for user-driven answering and should suit a custom call screen's purpose. It just won't work for any sort of automated call-control-type applications.

Also of note are all the possible caveats, including that this, too, will probably stop working in an update or two.

Riley C
  • 1,249
  • 1
  • 9
  • 5
  • `input keyevent 79` works fine on Sony Xperia 5.0. Works when calling from an activity or from a broadcast receiver. – nicolas May 31 '16 at 16:56
1

via the adb commands How to pick up a call by adb

Keep in mind that Android is Linux with a massive JVM on the front end. You can download a command line app and root the phone and now you have a regular Linux computer and command line that does all the normal things. Run scripts, you can even ssh to it (OpenVPN trick)

Community
  • 1
  • 1
0

Thanks @notz the answer of is working for me on the Lolillop. In order to keep this code working with the old android SDK, you can do this code:

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();  
    }  
  }  
}  
Xavi López
  • 27,550
  • 11
  • 97
  • 161
0

How to turn on Speaker Phone after automatically answering calls.

I solved my problem above with setSpeakerphoneOn. I think its worth posting here, since the use case for auto answering a phone call would often also require speakerphone to be useful. Thanks again to everyone on this thread, what an awesome job.

This works for me on Android 5.1.1 on my Nexus 4 without ROOT. ;)

Permission required:

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

Java Code:

// 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
}
Madhava Jay
  • 732
  • 1
  • 5
  • 14
  • 1
    Interesting. I am actually trying to answer call and turn on speaker together so this approach seems solve both :). I have similar question as some of the comments in other answers though: where does this code go? – fangmobile Jun 29 '16 at 16:59
-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.

Muzikant
  • 8,070
  • 5
  • 54
  • 88
  • 1
    While testing with a regular user profile, this brought up the in-call UI for me, asking me to swipe left/right to decline/answer or use a quick action/response. If the OP is _creating a custom incoming call screen_, this isn't really of any help unless it behaves differently under root, which I doubt as if it didn't behave well for a regular user, the call would probably simply fail and **not** trigger a different action. – Valter Jansons Nov 25 '14 at 19:35
-2

test this: firstly add the permissions then use killCall() to hangup use answerCall() to answer the call

<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();
    }
}
Michael
  • 646
  • 6
  • 16
-2

FYI if you're interested in how to END an ongoing call on Android O, Valter's Method 1: TelephonyManager.answerRingingCall() works if you change the method you invoke to be endCall.

It only requires the android.permission.CALL_PHONE permission.

Here's the code:

// 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);
    }
}
Francois Dermu
  • 4,437
  • 2
  • 22
  • 14