25

To detect when an outgoing call is answered, I tried creating a PhoneStateListener and listening for TelephonyManager's CALL_STATE_RINGING, CALL_STATE_OFFHOOK, and CALL_STATE_IDLE, from this question, but it does not seem to work, as explained below.

First, I registered the following permission in the manifest:

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

Then, a BroadcastReceiver called OutCallLogger that catches the NEW_OUTGOING_CALL event whenever an outgoing call is made:

<receiver android:name=".listener.OutCallLogger">
    <intent-filter>
        <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
    </intent-filter>
</receiver>

Next, my implementation of OutCallLogger. I set up a boolean called noCallListenerYet to avoid attaching a new PhoneStateListener to the TelephonyManager whenever onReceive() is invoked.

public class OutCallLogger extends BroadcastReceiver {

    private static boolean noCallListenerYet = true;

    @Override
    public void onReceive(final Context context, Intent intent) {
        number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
        if (noCallListenerYet) {
            final TelephonyManager tm = (TelephonyManager) context.getSystemService(
                    Context.TELEPHONY_SERVICE);
            tm.listen(new PhoneStateListener() {
                @Override
                public void onCallStateChanged(int state, String incomingNumber) {
                    switch (state) {
                        case TelephonyManager.CALL_STATE_RINGING:
                            Log.d(This.LOG_TAG, "RINGING");
                            break;
                        case TelephonyManager.CALL_STATE_OFFHOOK:
                            Log.d(This.LOG_TAG, "OFFHOOK");
                            break;
                        case TelephonyManager.CALL_STATE_IDLE:
                            Log.d(This.LOG_TAG, "IDLE");
                            break;
                        default:
                            Log.d(This.LOG_TAG, "Default: " + state);
                            break;
                    }
                }
            }, PhoneStateListener.LISTEN_CALL_STATE);
            noCallListenerYet = false;
        }
    }

}

Now, when I make an outgoing call in my device, CALL_STATE_RINGING is NEVER invoked. I always only get printouts of "IDLE" to "OFFHOOK" when the other line starts ringing, nothing when the call is answered, and a printout of "IDLE" again when the call is ended.

How can I reliably detect when an outgoing call is answered in Android, or is that even possible?

Community
  • 1
  • 1
Matthew Quiros
  • 13,385
  • 12
  • 87
  • 132

6 Answers6

13

Since Android 5.0 this is possible for system apps. But you need to use the hidden Android API.

I got it to work like this:

<uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE" />
<receiver android:name=".listener.OutCallLogger">
    <intent-filter>
        <action android:name="android.intent.action.PRECISE_CALL_STATE" />
    </intent-filter>
</receiver>
public class OutCallLogger extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        switch (intent.getIntExtra(TelephonyManager.EXTRA_FOREGROUND_CALL_STATE, -2) {
            case PreciseCallState.PRECISE_CALL_STATE_IDLE:
                Log.d(This.LOG_TAG, "IDLE");
                break;
            case PreciseCallState.PRECISE_CALL_STATE_DIALING:
                Log.d(This.LOG_TAG, "DIALING");
                break;
            case PreciseCallState.PRECISE_CALL_STATE_ALERTING:
                Log.d(This.LOG_TAG, "ALERTING");
                break;
            case PreciseCallState.PRECISE_CALL_STATE_ACTIVE:
                Log.d(This.LOG_TAG, "ACTIVE");
                break;
        }
    }
}

You can find all possible call states in PreciseCallState.java and all extras that the intent contains in TelephonyRegistry.java.

Tim S.
  • 310
  • 2
  • 9
  • Does this work only for Android 5.0? Or does it also support 4.0-4.4? –  Apr 25 '15 at 14:58
  • 3
    @stack This solution will only work for Android >= 5.0. The Telephony API was [changed](https://github.com/android/platform_frameworks_base/commit/c5ac15a3e11c03951e269b243674858411204b67) in Android 5.0 to make this possible. (The protection level for the required permission was later [changed from dangerous to signature|system](https://android.googlesource.com/platform/frameworks/base/+/5742335%5E!/). – Tim S. Apr 26 '15 at 21:29
  • Is this valid for Incoming call too?? – Tarun Aug 05 '15 at 07:08
  • 1
    Can't resolve symbol `TelephonyManager.EXTRA_FOREGROUND_CALL_STATE`! – Muhammad Babar Jan 06 '17 at 11:23
  • @MuhammadBabar You need to use either a custom `android.jar`(e.g. [from here](https://github.com/anggrayudi/android-hidden-api)) or load these symbols at runtime by using Reflection. – Tim S. Jan 10 '17 at 09:47
  • Tim, I think the `android-hidden-api` will not going to work if the debug app is being deployed on real device? – Muhammad Babar Jan 10 '17 at 13:04
  • @MuhammadBabar It does work on real devices (I'm using it on different phone models), but your app needs to be installed as a system app. And you generally need root access to be able to install an app as a system app. – Tim S. Jan 10 '17 at 16:07
  • @TimS. thanks and i think for that i also need to replace the firmware.jar with the custom-android.jar on phone! Right? – Muhammad Babar Jan 11 '17 at 05:57
  • Is it possible to change the protection level using `Reflections`? – Muhammad Babar Jan 11 '17 at 07:11
  • 2
    @MuhammadBabar The way I use it is I use Reflection to load the symbols at runtime and I install my app only on rooted phones as a system app. I haven't tried replacing any .jars myself, but I think you need to link against the `android-with-hidden-api.jar` at compile time. – Tim S. Jan 11 '17 at 14:16
  • @MuhammadBabar You can't change the protection level by using Reflection. If this was possible it would make protection levels useless. – Tim S. Jan 11 '17 at 14:26
  • Does anyone got a working example? I even tried it with making my app a system app. It doesn't work! – Mujtaba Sep 28 '17 at 15:50
3

It looks like the RINGING state is reached only by incoming calls. Outgoing calls change from IDLE to OFFHOOK, so looking at the Phone State maybe is not possible to achieve this.

I think that it could be possible using internal functions, look at this: What does the different Call states in the Android telephony stack represent?

Community
  • 1
  • 1
1087427
  • 465
  • 5
  • 18
0

Maybe try to use CallManager? Check out http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.3_r1/com/android/internal/telephony/CallManager.java. I also found CallManager.java among the SDK files on my computer. The following text from the linked page seems promising:

Register for getting notifications for change in the Call State Call.State This is 
called PreciseCallState because the call state is more precise than the Phone.State 
which can be obtained using the android.telephony.PhoneStateListener Resulting events 
will have an AsyncResult in Message.obj. AsyncResult.userData will be set to the obj 
argument here. The h parameter is held only by a weak reference.

1051
1052    public void registerForPreciseCallStateChanged(Handler h, int what, Object obj){
1053        mPreciseCallStateRegistrants.addUnique(h, what, obj);
1054    }

I haven't tried to code anything, so really don't know if it can do what you want, but I am curious to know.

hBrent
  • 1,696
  • 1
  • 17
  • 38
  • 1
    Any idea on how to access this CallManager class or related functions? i have tried phonestatelisteners and also tried reading call logs dynamically, but does not work. Want to identify when an outgoing call is answered, and play an audio file when it is answered. Apparently no way to do the same (?) – Pararth Jan 05 '15 at 08:58
  • According to this answer this approach is not working http://stackoverflow.com/a/5495850/3441905 – FindOut_Quran Jun 01 '16 at 02:20
  • But also this question is useful http://stackoverflow.com/questions/4745899/how-to-access-com-android-internal-telephony-callmanager – FindOut_Quran Jun 01 '16 at 02:30
0

Please pay your attention at:

tm.listen(new PhoneStateListener() {
            @Override
            public void onCallStateChanged(int state, String incomingNumber) {
                switch (state) {
                    case TelephonyManager.CALL_STATE_RINGING:
                        Log.d(This.LOG_TAG, "RINGING");
                        break;
                    case TelephonyManager.CALL_STATE_OFFHOOK:
                        Log.d(This.LOG_TAG, "OFFHOOK");
                        break;
                    case TelephonyManager.CALL_STATE_IDLE:
                        Log.d(This.LOG_TAG, "IDLE");
                        break;
                    default:
                        Log.d(This.LOG_TAG, "Default: " + state);
                        break;
                }
            }
        }, PhoneStateListener.LISTEN_CALL_STATE);

Do you see "incomingNumber" argument? Yes, that code just can only detect your phone-call-state when there is an incoming-phone-call to your device.

nđq
  • 388
  • 5
  • 14
-2

You could do the following... not very precise but could do the trick:

  1. You use the receiver for the android.intent.action.NEW_OUTGOING_CALL action
  2. When the receiver is called you store somewhere (for instance a static var) the NEW_OUTGOIN_CALL state and the time in ms when this happened (i.e. new Date().getTime())
  3. You use the another receiver for android.intent.action.PHONE_STATE and in the onReceive you do the following:

    if (intent.getAction().equals("android.intent.action.PHONE_STATE")) {
        TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        telephony.listen(new PhoneStateListener() {
            public void onCallStateChanged(int state, String incomingNumber) {
                  switch(state) {
                    case TelephonyManager.CALL_STATE_IDLE:
                    break;
                    case TelephonyManager.CALL_STATE_OFFHOOK:
                    break;
                    case TelephonyManager.CALL_STATE_RINGING:
                    break;
                   }
            } 
        }, PhoneStateListener.LISTEN_CALL_STATE);
    
    }
    

In the CALL_STATE_OFFHOOK case you check that the last stored state was NEW_OUTGOING_CALL and that the no more than aprox. 10 seconds have passed since the last state change. This means that the phone initiated a call at most 10 seconds ago and that now he is in the offhook state (meaning active call) without passing through idle or ringing. This could mean that the call was answered.

Slacky
  • 13
  • 1
  • 1
    I just tried this approach and I'm afraid it doesn't work. Here's my code: http://pastie.org/5846783. I created a broadcast receiver that accepts the two intents as you mentioned. I even included the call states that start with `EXTRA_STATE` just to make sure. However, making an outgoing call just never goes through either `EXTRA_STATE_RINGING` or `CALL_STATE_RINGING`--it immediately proceeds to `EXTRA`- and `CALL_STATE_OFFHOOK`, nothing is invoked when the outgoing call is answered, and `EXTRA`- and `CALL_STATE_IDLE` are called when it ends. Tested with a Nexus 4 on Android 4.2.1. – Matthew Quiros Jan 24 '13 at 13:37
  • 5
    This does not work. You get CALL_STATE_OFFHOOK when you hit the "Send" button, but you get nothing when the other side answers the call. – OferR Jan 30 '13 at 07:34
-3

Here your answer is that you have implemented CallStateListener in OutGoingCallReceiver which is wrong. You have to implement CallStateListener in PhoneStateListener

I have also tried this thing in my earlier project, I had faced the same issue, then I solved it like as below. I took 3 classes as below.

  1. AutoCallReceiver: Register the TelephonyManager with PhoneStateListener.LISTEN_CALL_STATE

  2. CallStateListener which listens three states as TelephonyManager.CALL_STATE_IDLE,TelephonyManager.CALL_STATE_OFFHOOK,TelephonyManager.CALL_STATE_RINGING

3.OutGoingCallReceiver which handles out going call

public class OutGoingCallReceiver extends BroadcastReceiver {  
    /* onReceive will execute on out going call */
    @Override  
    public void onReceive(Context context, Intent intent) {  
        Toast.makeText(context, "OutGoingCallReceiver", Toast.LENGTH_SHORT).show();
    }
}

public class CallStateListener extends PhoneStateListener {  
    String number=""; // variable for storing incoming/outgoing number
    Context mContext; // Application Context

    //Constructor that will accept Application context as argument
    public CallStateListener(Context context) {
        mContext=context;
    }

    // This function will automatically invoke when call state changed
    public void onCallStateChanged(int state,String incomingNumber)
    {
        boolean end_call_state=false; // this variable when true indicate that call is disconnected
        switch(state) {
            case TelephonyManager.CALL_STATE_IDLE:  
                // Handling Call disconnect state after incoming/outgoing call 
                Toast.makeText(mContext, "idle", Toast.LENGTH_SHORT).show();
                break;  

            case TelephonyManager.CALL_STATE_OFFHOOK:     
                // Handling outgoing call  
                Toast.makeText(mContext, "OFFHOOK", Toast.LENGTH_SHORT).show();
                // saving outgoing call state so that after disconnect idle state can act accordingly
                break;  

            case TelephonyManager.CALL_STATE_RINGING: 
                Toast.makeText(mContext, "RINGING", Toast.LENGTH_SHORT).show();     
                break;
        }
    }
}

public class AutoCallReceiver extends BroadcastReceiver {       
    /* onReceive will execute on call state change */
    @Override  
    public void onReceive(Context context, Intent intent) { 

    // Instantiating PhoneStateListener
    CallStateListener phoneListener=new CallStateListener(context);  

    // Instantiating TelephonyManager
        TelephonyManager telephony = (TelephonyManager)   
                         context.getSystemService(Context.TELEPHONY_SERVICE);  

            // Registering the telephony to listen CALL STATE change
        telephony.listen(phoneListener,PhoneStateListener.LISTEN_CALL_STATE);  
    }
}

<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<application ...>
    <receiver android:name=".OutGoingCallReceiver">
        <intent-filter>
            <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
        </intent-filter>
    </receiver>
    <receiver android:name=".AutoCallReceiver">
        <intent-filter>
            <action android:name="android.intent.action.PHONE_STATE" />
        </intent-filter>
    </receiver>
</application>
Max Base
  • 639
  • 1
  • 7
  • 15
Maneesh
  • 6,098
  • 5
  • 36
  • 55
  • Isn't this exactly what I did, only less specific? – Matthew Quiros Oct 30 '12 at 07:36
  • Sorry, I have updated my answer, you have to implement CallStateListener in PhoneStateListener – Maneesh Oct 30 '12 at 08:13
  • First of all, `AutoCallReceiver` does not catch outgoing calls. Secondly, the insides of `AutoCallReceiver` still do exactly what I wrote in the question. – Matthew Quiros Oct 30 '12 at 08:57
  • AutoCallReceiver is only for invoking phonestateListener which catch idle,offshook and ringing state, and you have implemented this in OutGoingCallReceiver which is wrong – Maneesh Oct 30 '12 at 09:22
  • 4
    I understand from your code that when an outgoing call is made, it fires `NEW_OUTGOING_CALL` so it goes to `OutGoingCallReceiver` which does nothing but show a `Toast`. Then, when an incoming call arrives (which isn't the stated problem), the `PHONE_STATE` intent is fired and the app crashes before `AutoCallReceiver` even executes because you didn't include the `READ_PHONE_STATE` permission. But anyway, you simply put the `PhoneStateListener` in an external class while I wrote it as an anonymous class. The intended effect is therefore the same. – Matthew Quiros Oct 30 '12 at 10:59