Cannot detect when outgoing call is answered in Android

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?


Solution 1:

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.

Solution 2:

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?

Solution 3:

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.

Solution 4:

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.