'Why does phone state listener get fired multiple times for same state for one call?

I am working on fetching recent call log as call get disconnected(outgoing , incoming) either answered or unanswered.

I am using Phone state listener to fire broadcast when call get disconnected but it getting fired multiple time for one call why so..??

So Please tell me how to fire receiver only once for one call.

here is my code

public class BroadcastReceiver  extends android.content.BroadcastReceiver{
    static boolean iscallended= true;
    Context mContext;
    TelephonyManager telephony;
     private static final String TAG = "CustomBroadcastReceiver";
    CustomPhoneStateListener customPhoneStateListener;

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub
        mContext = context;
        Bundle extras = intent.getExtras();
        if (extras != null) {
            String state = extras.getString(TelephonyManager.EXTRA_STATE);
            Log.w("DEBUG", state);

                telephony = (TelephonyManager)context.getSystemService(context.TELEPHONY_SERVICE);
               if(customPhoneStateListener==null)
               {

                customPhoneStateListener = new   CustomPhoneStateListener();
                telephony.listen(customPhoneStateListener,   PhoneStateListener.LISTEN_CALL_STATE);
               }



        }



    }
    private class CustomPhoneStateListener extends PhoneStateListener
    {
         private static final String TAG = "CustomPhoneStateListener";
         Handler handler=new Handler();

        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            // TODO Auto-generated method stub
            System.out.println(iscallended+  "  value of iscancelled ");
             switch (state) 
             {
             case TelephonyManager.CALL_STATE_RINGING:
                 if(!incomingNumber.equalsIgnoreCase(""))
                 {
                     //YOUR CODE HERE

                 }
                 break;

             case TelephonyManager.CALL_STATE_IDLE:
                 if(iscallended)
                 {
                     iscallended = false;
                 System.out.println("IDLE called");
                 Toast.makeText(mContext, "IDLE", Toast.LENGTH_SHORT).show();
                 Intent it = new Intent(mContext,MainActivity.class);
                 it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 mContext.startActivity(it);
                 }
                 break;
             default:
                 break;
             }
             super.onCallStateChanged(state, incomingNumber);
             telephony.listen(customPhoneStateListener, PhoneStateListener.LISTEN_NONE);
        }







    }

}

Here's receiver in manifest

 <receiver android:name="com.example.calllogs.BroadcastReceiver">
            <intent-filter >

                <action android:name="android.intent.action.PHONE_STATE"/>
            </intent-filter>


        </receiver>


Solution 1:[1]

Here is your answer :

https://stackoverflow.com/a/5497316/3479012

also specify permission in your manifest to access phone state.

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

also have a look at this http://karanbalkar.com/2014/02/detect-incoming-call-and-call-hangup-event-in-android/

Solution 2:[2]

Yes you will get that.

 private class MyPhoneStateListener extends PhoneStateListener {

    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
        switch (state) {
            //Hangup
            case TelephonyManager.CALL_STATE_IDLE:
                Log.d("IDLE", "Call Idle" + state);

                if (isCallEnded) {
                    isCallEnded = false;
                    AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity());
                                                    .setCancelable(false)
                            .setPositiveButton(getString(R.string.Yes), new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialogInterface, int i) {
                                    // your method
                                }
                            })
                            .setNegativeButton(getString(R.string.No), new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialogInterface, int i) {

                                }
                            });
                    alertBuilder.show();
                }
                break;
            //Outgoing
            case TelephonyManager.CALL_STATE_OFFHOOK:
                Log.d("OFFHOOK", "Call OffHook" + state);
                isCallEnded = true;
                break;
            //Incoming
            case TelephonyManager.CALL_STATE_RINGING:
                Log.d("RINGING", "Call Ringing" + state);
                break;
        }
    }
}

The above is sample, when you look at the device log you'll definitely see offhook multiple times as well as IDLE state.

Try to calculate that, it should be okay.

Solution 3:[3]

To avoid repetitive triggering, use MyPhoneStateListener as object and check lastCallState. Call makeMyPhoneStateListener from ServiceReceiver.

class MyPhoneStateListener () : PhoneStateListener() {

    companion object {

        var lastCallState: Int? = null
        lateinit var context: Context

        fun makeMyPhoneStateListener(_context: Context): MyPhoneStateListener
        {
            val myPhoneStateListener = MyPhoneStateListener()
            context = _context
            return myPhoneStateListener
        }
    }

    override fun onCallStateChanged(state: Int, incomingNumber: String) {

        when (state) {
            TelephonyManager.CALL_STATE_IDLE -> {

                if (lastCallState!= TelephonyManager.CALL_STATE_IDLE){
                    // some code for CALL_STATE_IDLE
                    lastCallState = TelephonyManager.CALL_STATE_IDLE
                }
            }
            TelephonyManager.CALL_STATE_OFFHOOK -> {

                if (lastCallState!= TelephonyManager.CALL_STATE_OFFHOOK) {

                    // some code for CALL_STATE_OFFHOOK
                    lastCallState = TelephonyManager.CALL_STATE_OFFHOOK
                }
            }
            TelephonyManager.CALL_STATE_RINGING -> {
                if (lastCallState!= TelephonyManager.CALL_STATE_RINGING) {

                    // some code for CALL_STATE_RINGING
                    lastCallState = TelephonyManager.CALL_STATE_RINGING    
                }
            }    
        }    
    }

Solution 4:[4]

I have overcame this problem using this simple trick

In your receiver class

public class Receiver extends BroadcastReceiver {

public static final String TAG = "MADARA";
private static boolean callAnswered = false;
private static boolean callRinging = false;
private static boolean callEnded = false;

@Override
public void onReceive(Context context, Intent intent) {

    if (intent.getAction().equals("android.intent.action.PHONE_STATE")){

        if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {

            if (!callAnswered){
                Log.d(TAG, "Call answered");
                callAnswered = true;

                new Handler(Looper.getMainLooper()).postDelayed(() -> callAnswered = false, 2000);
            }
        }
        else if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_IDLE)) {

            if (!callEnded){
                Log.d(TAG, "Call ended");
                callEnded = true;

                new Handler(Looper.getMainLooper()).postDelayed(() -> callEnded = false, 2000);
            }
        }
        else if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_RINGING)) {

            if (!callRinging){
                Log.d(TAG, "Call Ringing");
                callRinging = true;

                new Handler(Looper.getMainLooper()).postDelayed(() -> callRinging = false, 2000);
            }
        }
    }
}

}

Solution 5:[5]

you will find the below description in Android Devlopers documnetation here

Broadcast intent action indicating that the call state on the device has changed.

The EXTRA_STATE extra indicates the new call state. If a receiving app has Manifest.permission.READ_CALL_LOG permission, a second extra EXTRA_INCOMING_NUMBER provides the phone number for incoming and outgoing calls as a String.

If the receiving app has Manifest.permission.READ_CALL_LOG and Manifest.permission.READ_PHONE_STATE permission, it will receive the broadcast twice; one with the EXTRA_INCOMING_NUMBER populated with the phone number, and another with it blank. Due to the nature of broadcasts, you cannot assume the order in which these broadcasts will arrive, however you are guaranteed to receive two in this case. Apps which are interested in the EXTRA_INCOMING_NUMBER can ignore the broadcasts where EXTRA_INCOMING_NUMBER is not present in the extras (e.g. where Intent#hasExtra(String) returns false).

Now listener fired twice one for call log permission and the other for phone state permission, only one of them will return with phone number and the other will be Null.

So, you should first check if phone number returen empty or not like this

String stateString = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
String savedNumber = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);

Now You should first check if savedNumber is null or not

Here is full onReceive method

private static String savedNumber;
int state;
Context ctx;
@Override
    public void onReceive(Context context, @NonNull Intent intent) {
        Log.d("TAG101", "onReceive: " + intent.getAction().toString());
        ctx = context;
        state = -1;
        if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) {
            savedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
        } else {
            String stateString = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
            savedNumber = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
            if(savedNumber != null) {
                if (stateString.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {
                    state = TelephonyManager.CALL_STATE_OFFHOOK;
                } else if (stateString.equals(TelephonyManager.EXTRA_STATE_IDLE)) {
                    state = TelephonyManager.CALL_STATE_IDLE;
                    if (state == 0) {
                        onCallEnded();
                    }
                } else if (stateString.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
                    state = TelephonyManager.CALL_STATE_RINGING;
                }
            }
        }

    }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 EngineSense
Solution 3 David Buck
Solution 4 Nur Alam
Solution 5 Mohamed Ahmed