160

I'm trying to make an app like, when a call comes to the phone I want to detect the number. Below is what I tried, but it's not detecting incoming calls.

I want to run my MainActivity in background, how can I do that?

I had given the permission in manifest file.

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

Is there anything else should I provide in the manifest?

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_layout);
   }

   public class myPhoneStateChangeListener extends PhoneStateListener {
       @Override
       public void onCallStateChanged(int state, String incomingNumber) {
           super.onCallStateChanged(state, incomingNumber);
           if (state == TelephonyManager.CALL_STATE_RINGING) {
               String phoneNumber =   incomingNumber;
           }
       }
   }
}
Mike
  • 4,550
  • 4
  • 33
  • 47
Jesbin MJ
  • 3,219
  • 7
  • 23
  • 28

13 Answers13

368

Here's what I use to do this:

Manifest:

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

<!--This part is inside the application-->
    <receiver android:name=".CallReceiver" >
        <intent-filter>
            <action android:name="android.intent.action.PHONE_STATE" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
        </intent-filter>
    </receiver>

My base reusable call detector

package com.gabesechan.android.reusable.receivers;

import java.util.Date;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;

public abstract class PhonecallReceiver extends BroadcastReceiver {

    //The receiver will be recreated whenever android feels like it.  We need a static variable to remember data between instantiations

    private static int lastState = TelephonyManager.CALL_STATE_IDLE;
    private static Date callStartTime;
    private static boolean isIncoming;
    private static String savedNumber;  //because the passed incoming is only valid in ringing


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

        //We listen to two intents.  The new outgoing call only tells us of an outgoing call.  We use it to get the number.
        if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) {
            savedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
        }
        else{
            String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
            String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
            int state = 0;
            if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)){
                state = TelephonyManager.CALL_STATE_IDLE;
            }
            else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
                state = TelephonyManager.CALL_STATE_OFFHOOK;
            }
            else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)){
                state = TelephonyManager.CALL_STATE_RINGING;
            }


            onCallStateChanged(context, state, number);
        }
    }

    //Derived classes should override these to respond to specific events of interest
    protected abstract void onIncomingCallReceived(Context ctx, String number, Date start);
    protected abstract void onIncomingCallAnswered(Context ctx, String number, Date start);
    protected abstract void onIncomingCallEnded(Context ctx, String number, Date start, Date end);

    protected abstract void onOutgoingCallStarted(Context ctx, String number, Date start);      
    protected abstract void onOutgoingCallEnded(Context ctx, String number, Date start, Date end);

    protected abstract void onMissedCall(Context ctx, String number, Date start);

    //Deals with actual events

    //Incoming call-  goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
    //Outgoing call-  goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up
    public void onCallStateChanged(Context context, int state, String number) {
        if(lastState == state){
            //No change, debounce extras
            return;
        }
        switch (state) {
            case TelephonyManager.CALL_STATE_RINGING:
                isIncoming = true;
                callStartTime = new Date();
                savedNumber = number;
                onIncomingCallReceived(context, number, callStartTime);
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                //Transition of ringing->offhook are pickups of incoming calls.  Nothing done on them
                if(lastState != TelephonyManager.CALL_STATE_RINGING){
                    isIncoming = false;
                    callStartTime = new Date();
                    onOutgoingCallStarted(context, savedNumber, callStartTime);                     
                }
                else
                {
                    isIncoming = true;
                    callStartTime = new Date();
                    onIncomingCallAnswered(context, savedNumber, callStartTime); 
                }

                break;
            case TelephonyManager.CALL_STATE_IDLE:
                //Went to idle-  this is the end of a call.  What type depends on previous state(s)
                if(lastState == TelephonyManager.CALL_STATE_RINGING){
                    //Ring but no pickup-  a miss
                    onMissedCall(context, savedNumber, callStartTime);
                }
                else if(isIncoming){
                    onIncomingCallEnded(context, savedNumber, callStartTime, new Date());                       
                }
                else{
                    onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());                                               
                }
                break;
        }
        lastState = state;
    }
}

Then to use it, simply derive a class from it and implement a few easy functions, whichever call types you care about:

public class CallReceiver extends PhonecallReceiver {

    @Override
    protected void onIncomingCallReceived(Context ctx, String number, Date start)
    {
        //
    }

    @Override
    protected void onIncomingCallAnswered(Context ctx, String number, Date start)
    {
        //
    }

    @Override
    protected void onIncomingCallEnded(Context ctx, String number, Date start, Date end)
    {
        //
    }

    @Override
    protected void onOutgoingCallStarted(Context ctx, String number, Date start)
    {
        //
    } 

    @Override 
    protected void onOutgoingCallEnded(Context ctx, String number, Date start, Date end)
    {
        //
    }

    @Override
    protected void onMissedCall(Context ctx, String number, Date start)
    {
        //
    }

}

In addition you can see a writeup I did on why the code is like it is on my blog. Gist link: https://gist.github.com/ftvs/e61ccb039f511eb288ee

EDIT: Updated to simpler code, as I've reworked the class for my own use

ysimonx
  • 83
  • 10
Gabe Sechan
  • 90,003
  • 9
  • 87
  • 127
  • @Gabe Sechan outgoing call number is not display can you plzz tell what change required for outgoing call number – Mahesh Feb 27 '14 at 07:10
  • 2
    This code doesn't display anything. What it does is call you when an outgoing call starts/ends, and passes you the number, time started, and time finished. Actually displaying it is your job, because I have no way of knowing how you want that done. – Gabe Sechan Feb 27 '14 at 19:00
  • 3
    @GabeSechan: Awesome! can you please guide me to handle call-waiting situation ? – Mehul Joisar May 21 '14 at 06:23
  • This works well, but why doesn't the phone notify the listener about an incoming call if the phone is asleep when the call comes in? Is there a work around this? – Jeremy Oct 24 '14 at 07:06
  • Hmm, I'd have to look into that. Were you launching an Activity/Service from here, or were you just doing some work directly in the handler functions? In the first case, it may need a wakelock. In the second it ought to work, I've done it before (it would write to the database when calls ended, and it worked even when asleep). – Gabe Sechan Oct 25 '14 at 19:48
  • Thanks for this wonderful code. Just a small query. Shouldn't the overriden methods in the subclass have Context in their signature? – Rajat Sharma Dec 17 '14 at 09:47
  • 8
    Just to add to this, it did not work when the app was not in foreground or background until I added this in the receiver: "android:enabled="true" – Rajat Sharma Dec 17 '14 at 11:30
  • You're right. The original version I posted didn't, I added that in a refactor to make things less clunky. I'll fix that. As for enabled-true should be the default, according to the docs. – Gabe Sechan Dec 17 '14 at 13:02
  • Are static variables enough to guarantee that the state is kept? If the application is no longer in memory, I believe state is lost, even when using static variables. – Roar Skullestad Mar 05 '15 at 11:03
  • 1
    Static variables will stay around until the application is kicked out of memory (which can be quite a long time, depending on if services are running and general phone memory conditions). But yes, they can be lost. You can write it to disk, say via shared preference but that can cause you to have false results as well- it would prevent you from having your data properly cleared in several cases, such as around phone reboot. For my use case the rare null and lost data was better than incorrect data. Feel free to twiddle with this to your needs. – Gabe Sechan Mar 05 '15 at 18:52
  • Looks promising! I will give this a try. I wonder whether it would be better if it called an interface. – ftvs Aug 05 '15 at 10:43
  • 1
    @GabeSechan : There seems to be a bug in it. `lastState` should not be initialized to `CALL_STATE_IDLE`. I am missing few calls when my app gets killed while the current state is `RINGING`. Because when it becomes `IDLE` again on call ends, static variable is re-initialized to `CALL_STATE_IDLE` and it debounces doing nothing. So we lose the reference to `lastState`. – Heisenberg Sep 21 '15 at 15:55
  • 1
    this is all very pretty but it doenst work in all situations! wat if you receive a phone call during another phone call? ya, the states will be screwed and it doenst work anymore – xanexpt Feb 09 '16 at 13:26
  • But can it work for messages too of course after giving permissions in manifest file. – CocoCrisp Jul 18 '16 at 07:02
  • I'm using the same code and running it on my OnePlus One. The receiver does not get a call back at all. I've registered the receiver in the Manifest and also added the necessary permission. Using the exact same code. Also, I tried to 'log' if the receiver gets the callback at all. But that isn't happening too. Does someone have a solution? – Arjun Issar Sep 12 '16 at 10:58
  • 1
    Got it! There was a problem related to runtime permissions. READ_PHONE_STATE is a dangerous permission and needs to be asked on run time. Crazy solution to the question, very good code. @GabeSechan – Arjun Issar Sep 12 '16 at 11:26
  • this doesn't work at all for me... in `MainActivity.onCreate(){}` I've placed `receiver = new CallReceiver(this);` and declared `private CallReceiver receiver;` globally but nothing runs – Mr Heelis Sep 22 '16 at 13:44
  • Because you need to register it in the manifest. You don't create an instance of it. Unless you only want to know while you're activity ruins, in which case you need to register it programmatically – Gabe Sechan Sep 22 '16 at 13:46
  • Marshamellow+ requires run time permission – Ahmed Hegazy Oct 10 '16 at 19:50
  • 1
    what about dual sim calls? – Choletski Nov 15 '16 at 10:46
  • 1
    you don't have `onOutgoingCallAnswered` – Choletski Nov 23 '16 at 12:40
  • I also wanted to ask about the status for `onOutgoingCallAnswered` – atifali Nov 23 '16 at 13:11
  • 1
    At least at the time I wrote this there was no way to tell if the other side answered. Haven't revisited lately to see if new apis have improved it – Gabe Sechan Nov 23 '16 at 15:45
  • @KirillStoianov ReadPhoneState requires runtime permission. You'd need to add that check. Other than that, I don't know of any reason it wouldn't, but I haven't tested it in a year or so, I haven't done any work that required this functionality. – Gabe Sechan Mar 29 '17 at 15:30
  • Nice solution, but it remembers the previous state only while your app was not removed from memory (see static fields). To preserve the states across app restarts you need to save the states somewhere where it is persisted. (E.g. shared prefs or database.) – racs Jun 05 '17 at 03:23
  • @racs the problem with that is when the state becomes wrong. For example if you'd I a call and the phone dies. When you reboot it will think you're in a call. My preferred failure method for whay I was doing was to risk losing a call over risking stake data. For other problems it may differ. Feel free to adjust to your needs – Gabe Sechan Jun 05 '17 at 03:29
  • @GabeSechan Depends on the use case, I guess. This problem is not easy to solve, I have to admit. Unfortunately it could happen that your app is removed from memory while the call is on (because the OS starts running out of memory) and get started again when the call ends by reacting on the incoming broadcast about the changed telephony state. In this scenario onOutgoingCallEnded callback is called with empty data instead of onIncomingCallEnded or onMissedCall. – racs Jun 05 '17 at 03:47
  • Nice abstraction but it does not handle multiple calls (with my carrier I can answer a second call while already in a call). But it seems Android doesn't give the tools to manage this multiple line cases. – Mathieu H. Jul 24 '17 at 14:07
  • @GabeSechan how will I start the call receiver without the main activity. please don't mind I am so new in android. – Sumon Bappi Jul 25 '17 at 20:23
  • @GabeSechan I got it, - `startService(new Intent(this, CallReceiver.class));` – Sumon Bappi Jul 25 '17 at 21:03
  • Will this work if the app targets O, as there are new broadcast receiver limitations now? https://developer.android.com/preview/features/background.html#broadcasts – Bob Aug 16 '17 at 09:14
  • Does this solution require rooted system? – M. Stefanczuk Oct 12 '17 at 13:05
  • 1
    @GabeSechan One question should I get the information for Dual sim mobile & on which sim receives call? – Suhas Bachewar Jun 01 '18 at 09:48
  • @GabeSechan doesn't work on some devices unless you register `PhonecallReceiver` with `registerReceiver`. – amrezzd Aug 20 '18 at 11:37
  • @AmirRezazadeh - Just adding some detail to your comment -- Android 8 (API 26+) is when you must start registering receivers programmatically and not through the Manifest. Here is a blog about it: https://android.jlelse.eu/android-o-impact-on-running-apps-and-developer-viewpoint-b9f23047f306. It is also mentioned on the Developers.Android site in the change log for Android 8.0 – Mira_Cole Aug 24 '18 at 16:05
  • @Mira_Cole There are some receivers that can still be added through the manifest. ACTION_NEW_OUTGOING_CALL and ACTION_PHONE_STATE_CHANGED are two of them. Tested it on Android 9 (API 28) and still works. [For more information](https://developer.android.com/guide/components/broadcast-exceptions) – J. Jefferson Sep 28 '18 at 17:02
  • how can I detect phone calls when an app is not in the background? @GabeSechan – Brinda Rathod Nov 19 '18 at 05:44
  • nice code, works fine!!! Also can you explain how to get which sim receives the call if the phone is a dual sim phone please? – mili Jan 29 '19 at 03:44
  • Unfortunately the code doesn't' work for me (Android Studio 8). Modified the manifest, copied PhonecallReciver.java and wrote a simple CallReciver.java that logs incoming phone calls but nothing happens. – Jaitnium Mar 07 '19 at 21:59
  • @Jaitnium At this point the snippet is 6 years old. You need to add in runtime permisisons (not a thing back then), and some broadcasts can no longer be registered for in the manifest. – Gabe Sechan Mar 07 '19 at 22:03
  • @GabeSechan How do I do that? – Jaitnium Mar 07 '19 at 22:30
  • Never mind, I followed this tutorial: https://www.youtube.com/watch?v=DXPwxOc9-gw – Jaitnium Mar 07 '19 at 23:19
  • On API level 28, null is always passed to onIncomingCallStarted. – Jaitnium Jun 25 '19 at 22:54
  • can i use this for flutter method channel – Asbah Riyas May 20 '20 at 05:52
  • PROCESS_OUTGOING_CALLS is sensitive permission, google play gonna reject your application if you use it – FarshidABZ Jun 30 '20 at 08:02
  • @FarshidABZ It wasn't 7 years ago when this answer was written. These days the right answer is probably CallRedirectionService. – Gabe Sechan Jun 30 '20 at 15:03
  • @GabeSechan of course, I just write for who looks for an answer these days. like Asbah. but of course, your answer was correct. – FarshidABZ Jul 01 '20 at 05:56
  • @GabeSechan when i call ussd code like *111# this receiver doest not work and can't detect any state but when i call a number works fine can you tell what is the problem? – Mateen Chaudhry Jul 26 '20 at 08:33
  • 1
    @MateenChaudhry USSD code isn't a call, so this wouldn't work. Its a separate protocol. Consider it like ICMP is to TCP or UDP. Android doesn't really support USSD, there's no APIs for it. A dialer app can try and send one, but there's no way to do it programatically. You should really avoid using USSD, there's generally better ways to do it- USSD should be considered deprecated, its basically all legacy systems these days. – Gabe Sechan Jul 27 '20 at 03:56
  • I detect some bugs , fixed them in this link : https://stackoverflow.com/a/68077549/10598138 – Javad Shirkhani Jun 22 '21 at 04:30
  • Hi @GabeSechan. I dont actually get it. Wont it stop working the when the app is killed? – Sambhav Khandelwal May 05 '22 at 04:23
  • And, it is not working even if the app is open – Sambhav Khandelwal May 05 '22 at 04:31
26
private MyPhoneStateListener phoneStateListener = new MyPhoneStateListener();

to register

TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);

and to unregister

TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
Niklas
  • 23,674
  • 33
  • 131
  • 170
stinepike
  • 54,068
  • 14
  • 92
  • 112
  • where should i use this in mainactivity? – Jesbin MJ Mar 22 '13 at 06:24
  • place this in the class where are you listening the change.. normally registers are done in oncreate and unregister in ondestroy.. declare the object globally in the class – stinepike Mar 22 '13 at 06:26
  • 1
    This option doesn't require permissions. – Mike Oct 24 '16 at 14:47
  • while Gabe's solution is better fit for more intrusive features i.e. Viber sort of app, this is the best solution for those in need to simply react to user actions of going into a phone call. Requesting runtime permission in cases like this is most likely an overkill and might not be well received by the user. – bosphere Jan 23 '17 at 02:02
  • 1
    I want to do something, when call is coming, how to do with this code? – Noor Hossain Apr 10 '20 at 15:42
  • I want to do something, when call is coming, how to do with this code? – Noor Hossain Apr 10 '20 at 15:42
19

With Android P - Api Level 28: You need to get READ_CALL_LOG permission

Restricted access to call logs

Android P moves the CALL_LOG, READ_CALL_LOG, WRITE_CALL_LOG, and PROCESS_OUTGOING_CALLS permissions from the PHONE permission group to the new CALL_LOG permission group. This group gives users better control and visibility to apps that need access to sensitive information about phone calls, such as reading phone call records and identifying phone numbers.

To read numbers from the PHONE_STATE intent action, you need both the READ_CALL_LOG permission and the READ_PHONE_STATE permission. To read numbers from onCallStateChanged(), you now need the READ_CALL_LOG permission only. You no longer need the READ_PHONE_STATE permission.

Karthikeyan
  • 196
  • 2
  • 10
atasoyh
  • 3,045
  • 6
  • 31
  • 57
  • for those who are only adding ```READ_CALL_LOG``` in ```AndroidManifest.xml``` focus on adding a permission request in ```MainActivity```. – Piyush Jun 06 '20 at 07:23
  • I work on some alternate methods. we can get the mobile number from the notification listener. But there is an issue one some devices that the incoming call does not handle as a notification. so I didn't get mobile number. –  Dec 29 '20 at 09:36
  • If I use `READ_CALL_LOG` permission in my app, I should fill declaration on Google Play Console. What do I need to choose from a list if I use `READ_CALL_LOG` permission for searching a name from a phonebook? – Yury Matatov Jul 01 '21 at 05:25
18

UPDATE: The really awesome code posted by Gabe Sechan no longer works unless you explicitly request the user to grant the necessary permissions. Here is some code that you can place in your main activity to request these permissions:

    if (getApplicationContext().checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
            != PackageManager.PERMISSION_GRANTED) {
        // Permission has not been granted, therefore prompt the user to grant permission
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.READ_PHONE_STATE},
                MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
    }

    if (getApplicationContext().checkSelfPermission(Manifest.permission.PROCESS_OUTGOING_CALLS)
            != PackageManager.PERMISSION_GRANTED) {
        // Permission has not been granted, therefore prompt the user to grant permission
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.PROCESS_OUTGOING_CALLS},
                MY_PERMISSIONS_REQUEST_PROCESS_OUTGOING_CALLS);
    }

ALSO: As someone mentioned in a comment below Gabe's post, you have to add a little snippet of code, android:enabled="true, to the receiver in order to detect incoming calls when the app is not currently running in the foreground:

    <!--This part is inside the application-->
    <receiver android:name=".CallReceiver" android:enabled="true">
        <intent-filter>
            <action android:name="android.intent.action.PHONE_STATE" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
        </intent-filter>
    </receiver>
Karthikeyan
  • 196
  • 2
  • 10
topherPedersen
  • 541
  • 7
  • 15
  • What if the application does not have any Activity and only the broadcastreceiver and a service. Then where do we write this code to obtain permission from the user as the broadcastreceiver will not be called till this permission is given. – user2779311 Apr 13 '18 at 11:02
  • 2
    You at least need a MainActivity even if it's only opened once. Take my call blocking app RoboStop for example: When the user downloads the app for the first time, and then clicks on the app icon to launch the app, they are then prompted to grant my app the necessary permissions. The app also features a button to enable/disable call blocking, but the user doesn't need to launch the app/activity again, the call blocking will take place in the background without the user having to launch the app/activity again. – topherPedersen Apr 14 '18 at 17:23
8

Just to update Gabe Sechan's answer. If your manifest asks for permissions to READ_CALL_LOG and READ_PHONE_STATE, onReceive will called TWICE. One of which has EXTRA_INCOMING_NUMBER in it and the other doesn't. You have to test which has it and it can occur in any order.

https://developer.android.com/reference/android/telephony/TelephonyManager.html#ACTION_PHONE_STATE_CHANGED

Jaitnium
  • 621
  • 1
  • 13
  • 27
5

this may helps you and also add require permision

public class PhoneListener extends PhoneStateListener
{
    private Context context;
    public static String getincomno;

    public PhoneListener(Context c) {
        Log.i("CallRecorder", "PhoneListener constructor");
        context = c;
    }

    public void onCallStateChanged (int state, String incomingNumber)
    {

        if(!TextUtils.isEmpty(incomingNumber)){
        // here for Outgoing number make null to get incoming number
        CallBroadcastReceiver.numberToCall = null;
        getincomno = incomingNumber;
        }

        switch (state) {
        case TelephonyManager.CALL_STATE_IDLE:

            break;
        case TelephonyManager.CALL_STATE_RINGING:
            Log.d("CallRecorder", "CALL_STATE_RINGING");
            break;
        case TelephonyManager.CALL_STATE_OFFHOOK:

            break;
        }
    }
}
Ankitkumar Makwana
  • 3,475
  • 3
  • 19
  • 45
3

Here is a simple method which can avoid the use of PhonestateListener and other complications.
So here we are receiving the 3 events from android such as RINGING,OFFHOOK and IDLE. And in order to get the all possible state of call,we need to define our own states like RINGING, OFFHOOK, IDLE, FIRST_CALL_RINGING, SECOND_CALL_RINGING. It can handle every states in a phone call.
Please think in a way that we are receiving events from android and we will define our on call states. See the code.

public class CallListening  extends BroadcastReceiver {
    private static final String TAG ="broadcast_intent";
    public static String incoming_number;
    private String current_state,previus_state,event;
    public static Boolean dialog= false;
    private Context context;
    private SharedPreferences sp,sp1;
    private SharedPreferences.Editor spEditor,spEditor1;
    public void onReceive(Context context, Intent intent) {
        //Log.d("intent_log", "Intent" + intent);
        dialog=true;
        this.context = context;
        event = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
        incoming_number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
        Log.d(TAG, "The received event : "+event+", incoming_number : " + incoming_number);
        previus_state = getCallState(context);
        current_state = "IDLE";
        if(incoming_number!=null){
            updateIncomingNumber(incoming_number,context);
        }else {
            incoming_number=getIncomingNumber(context);
        }
        switch (event) {
            case "RINGING":
                Log.d(TAG, "State : Ringing, incoming_number : " + incoming_number);
            if((previus_state.equals("IDLE")) || (previus_state.equals("FIRST_CALL_RINGING"))){
                    current_state ="FIRST_CALL_RINGING";
                }
                if((previus_state.equals("OFFHOOK"))||(previus_state.equals("SECOND_CALL_RINGING"))){
                    current_state = "SECOND_CALL_RINGING";
                }

                break;
            case "OFFHOOK":
                Log.d(TAG, "State : offhook, incoming_number : " + incoming_number);
                if((previus_state.equals("IDLE")) ||(previus_state.equals("FIRST_CALL_RINGING")) || previus_state.equals("OFFHOOK")){
                    current_state = "OFFHOOK";
                }
                if(previus_state.equals("SECOND_CALL_RINGING")){
                    current_state ="OFFHOOK";
                    startDialog(context);
                }
                break;
            case "IDLE":
                Log.d(TAG, "State : idle and  incoming_number : " + incoming_number);
                if((previus_state.equals("OFFHOOK")) || (previus_state.equals("SECOND_CALL_RINGING")) || (previus_state.equals("IDLE"))){
                    current_state="IDLE";
                }
                if(previus_state.equals("FIRST_CALL_RINGING")){
                    current_state = "IDLE";
                    startDialog(context);
                }
                updateIncomingNumber("no_number",context);
                Log.d(TAG,"stored incoming number flushed");
                break;
        }
        if(!current_state.equals(previus_state)){
            Log.d(TAG, "Updating  state from "+previus_state +" to "+current_state);
            updateCallState(current_state,context);

        }
    }
    public void startDialog(Context context) {
        Log.d(TAG,"Starting Dialog box");
        Intent intent1 = new Intent(context, NotifyHangup.class);
        intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent1);

    }
    public void updateCallState(String state,Context context){
        sp = PreferenceManager.getDefaultSharedPreferences(context);
        spEditor = sp.edit();
        spEditor.putString("call_state", state);
        spEditor.commit();
        Log.d(TAG, "state updated");

    }
    public void updateIncomingNumber(String inc_num,Context context){
        sp = PreferenceManager.getDefaultSharedPreferences(context);
        spEditor = sp.edit();
        spEditor.putString("inc_num", inc_num);
        spEditor.commit();
        Log.d(TAG, "incoming number updated");
    }
    public String getCallState(Context context){
        sp1 = PreferenceManager.getDefaultSharedPreferences(context);
        String st =sp1.getString("call_state", "IDLE");
        Log.d(TAG,"get previous state as :"+st);
        return st;
    }
    public String getIncomingNumber(Context context){
        sp1 = PreferenceManager.getDefaultSharedPreferences(context);
        String st =sp1.getString("inc_num", "no_num");
        Log.d(TAG,"get incoming number as :"+st);
        return st;
    }
}
Karthikeyan
  • 196
  • 2
  • 10
ARUNBALAN NV
  • 1,634
  • 4
  • 17
  • 39
3

I fixed Gabe Sechan answer, I used the following code and it worked properly. I noticed when a receiver intent has the "incoming_number" key, I can get the phone number. So I filtered incoming intent and used EXTRA_INCOMING_NUMBER and EXTRA_PHONE_NUMBER to get the phone number.

public class ChangeCallStateListener extends BroadcastReceiver {

    private static String lastState = TelephonyManager.EXTRA_STATE_IDLE;
    private static Date callStartTime;
    private static boolean isIncoming;
    private static String savedNumber;  //because the passed incoming is only valid in ringing

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("CallObserver", "CallReceiver is starting ....");

        List<String> keyList = new ArrayList<>();
        Bundle bundle = intent.getExtras();
        if (bundle != null) {
            keyList = new ArrayList<>(bundle.keySet());
            Log.e("CallObserver", "keys : " + keyList);
        }

        if (keyList.contains("incoming_number")) {
            String phoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
            String phoneIncomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
            String phoneOutgoingNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);

            String phoneNumber = phoneOutgoingNumber != null ? phoneOutgoingNumber : (phoneIncomingNumber != null ? phoneIncomingNumber : "");

            if (phoneState != null && phoneNumber != null) {
                if (lastState.equals(phoneState)) {
                    //No change, debounce extras
                    return;
                }
                Log.e("CallObserver", "phoneState = " + phoneState);
                if (TelephonyManager.EXTRA_STATE_RINGING.equals(phoneState)) {
                    isIncoming = true;
                    callStartTime = new Date();
                    //
                    lastState = TelephonyManager.EXTRA_STATE_RINGING;
                    if (phoneNumber != null) {
                        savedNumber = phoneNumber;
                    }


                    onIncomingCallStarted(context, savedNumber, callStartTime);
                } else if (TelephonyManager.EXTRA_STATE_IDLE.equals(phoneState)) {

                    if (lastState.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
                        //
                        lastState = TelephonyManager.EXTRA_STATE_IDLE;
                        onMissedCall(context, savedNumber, callStartTime);
                    } else {
                        if (isIncoming) {
                            //
                            lastState = TelephonyManager.EXTRA_STATE_IDLE;
                            onIncomingCallEnded(context, savedNumber, callStartTime, new Date());
                        } else {
                            //
                            lastState = TelephonyManager.EXTRA_STATE_IDLE;
                            Log.d("CallObserver", "onOutgoingCallEnded called !! : ");
                            onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());
                        }

                    }
                } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(phoneState)) {
                    if (lastState.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
                        isIncoming = true;
                    } else {
                        isIncoming = false;
                    }
                    callStartTime = new Date();
                    savedNumber = phoneNumber;
                    //
                    lastState = TelephonyManager.EXTRA_STATE_OFFHOOK;
                    onOutgoingCallStarted(context, savedNumber, callStartTime);
                }
            }
        }

    }


    protected void onIncomingCallStarted(Context ctx, String number, Date start) {
        Log.d("CallObserver", "onIncomingCallStarted  :  " + " number is  : " + number);
    }

    protected void onOutgoingCallStarted(Context ctx, String number, Date start) {
        Log.d("CallObserver", "onOutgoingCallStarted  :  " + " number is  : " + number);
    }

    protected void onIncomingCallEnded(Context context, String number, Date start, Date end) {
    }

    protected void onOutgoingCallEnded(Context context , String number, Date start, Date end) {
    }

    protected void onMissedCall(Context context, String number, Date start) {
    }

}
  • Don't forget to get run time permission.

      <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    
Javad Shirkhani
  • 343
  • 6
  • 11
1

@Gabe Sechan, thanks for your code. It works fine except the onOutgoingCallEnded(). It is never executed. Testing phones are Samsung S5 & Trendy. There are 2 bugs I think.

1: a pair of brackets is missing.

case TelephonyManager.CALL_STATE_IDLE: 
    // Went to idle-  this is the end of a call.  What type depends on previous state(s)
    if (lastState == TelephonyManager.CALL_STATE_RINGING) {
        // Ring but no pickup-  a miss
        onMissedCall(context, savedNumber, callStartTime);
    } else {
        // this one is missing
        if(isIncoming){
            onIncomingCallEnded(context, savedNumber, callStartTime, new Date());                       
        } else {
            onOutgoingCallEnded(context, savedNumber, callStartTime, new Date());                                               
        }
    }
    // this one is missing
    break;

2: lastState is not updated by the state if it is at the end of the function. It should be replaced to the first line of this function by

public void onCallStateChanged(Context context, int state, String number) {
    int lastStateTemp = lastState;
    lastState = state;
    // todo replace all the "lastState" by lastStateTemp from here.
    if (lastStateTemp  == state) {
        //No change, debounce extras
        return;
    }
    //....
}

Additional I've put lastState and savedNumber into shared preference as you suggested.

Just tested it with above changes. Bug fixed at least on my phones.

Derk Jan Speelman
  • 11,291
  • 4
  • 29
  • 45
Diiiiii
  • 209
  • 3
  • 8
1

Please use the below code. It will help you to get the incoming number with other call details.

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<TextView
    android:id="@+id/call"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:text="@string/hello_world" />

</RelativeLayout>

MainActivity.java

public class MainActivity extends Activity {

private static final int MISSED_CALL_TYPE = 0;
private TextView txtcall;

@Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    txtcall = (TextView) findViewById(R.id.call);

    StringBuffer sb = new StringBuffer();
    Cursor managedCursor = managedQuery(CallLog.Calls.CONTENT_URI, null,
            null, null, null);
    int number = managedCursor.getColumnIndex(CallLog.Calls.NUMBER);
    int type = managedCursor.getColumnIndex(CallLog.Calls.TYPE);
    int date = managedCursor.getColumnIndex(CallLog.Calls.DATE);
    int duration = managedCursor.getColumnIndex(CallLog.Calls.DURATION);
    sb.append("Call Details :");
    while (managedCursor.moveToNext()) {
        String phNumber = managedCursor.getString(number);
        String callType = managedCursor.getString(type);
        String callDate = managedCursor.getString(date);
        Date callDayTime = new Date(Long.valueOf(callDate));
        String callDuration = managedCursor.getString(duration);
        String dir = null;
        int dircode = Integer.parseInt(callType);
        switch (dircode) {

        case CallLog.Calls.OUTGOING_TYPE:
            dir = "OUTGOING";
            break;

        case CallLog.Calls.INCOMING_TYPE:
            dir = "INCOMING";
            break;

        case CallLog.Calls.MISSED_TYPE:
            dir = "MISSED";
            break;
        }
        sb.append("\nPhone Number:--- " + phNumber + " \nCall Type:--- "
                + dir + " \nCall Date:--- " + callDayTime
                + " \nCall duration in sec :--- " + callDuration);
        sb.append("\n----------------------------------");
    }
    managedCursor.close();
    txtcall.setText(sb);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
}

} 

and in your manifest request for following permissions:

<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
sud007
  • 5,824
  • 4
  • 56
  • 63
user2173696
  • 151
  • 1
  • 2
1

You need a BroadcastReceiver for ACTION_PHONE_STATE_CHANGED This will call your received whenever the phone-state changes from idle, ringing, offhook so from the previous value and the new value you can detect if this is an incoming/outgoing call.

Required permission would be:

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

But if you also want to receive the EXTRA_INCOMING_NUMBER in that broadcast, you'll need another permission: "android.permission.READ_CALL_LOG"

And the code something like this:

val receiver: BroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d(TAG, "onReceive")
    }
}

override fun onResume() {
    val filter = IntentFilter()
    filter.addAction("android.intent.action.PHONE_STATE")
    registerReceiver(receiver, filter)
    super.onResume()
}

override fun onPause() {
    unregisterReceiver(receiver)
    super.onPause()
}

and in receiver class, we can get current state by reading intent like this:

intent.extras["state"]

the result of extras could be:

RINGING -> If your phone is ringing

OFFHOOK -> If you are talking with someone (Incoming or Outcoming call)

IDLE -> if call ended (Incoming or Outcoming call)

With PHONE_STATE broadcast we don't need to use PROCESS_OUTGOING_CALLS permission or deprecated NEW_OUTGOING_CALL action.

FarshidABZ
  • 3,860
  • 4
  • 32
  • 63
0

Refer to the answer by Gabe Sechan. As mentioned, in the case of an Outgoing call, we have the following state change: IDLE -> OFFHOOK -> IDLE. In Gabe's original answer, savedNumber is only set if the phone state becomes RINGING which won't be true for an Outgoing call. A small fix to to also set savedNumber when the phone state becomes OFFHOOK:

case TelephonyManager.CALL_STATE_OFFHOOK:
    if(lastState != TelephonyManager.CALL_STATE_RINGING){
        //IDLE to OFFHOOK for example.
        isIncoming = false;
        callStartTime = new Date();
        savedNumber = number;
        onOutgoingCallStarted(context, savedNumber, callStartTime);
    }
...

This fix allows the dialed number to be passed to Outgoing call methods in the same way that the incoming number is passed to Incoming call or Missed call methods.

0

Hack Alert :)

If you are just interested in the incoming call event, consider using the AudioManager and listening to focus changes. Advantage - no permission is required. Disadvantage - won't work in silent mode... in that case, we will increase the volume of the incoming call to the minimum to still get the Audio focus event.

 /**
 * Register a callback to [AudioManager] to identify an incoming call.
 */
private fun registerAudioFocusChangeListener(){
   val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    val incomingCallVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING)
    if(incomingCallVolume == 0) {
        // Hack Alert :)
        // The user has muted the phone calls, if we still want to intercept the event,
        // we set the volume of the incoming call to Minumum otherwise we will not get
        // Audio focus event...
        try {
            audioManager.adjustVolume(
                AudioManager.ADJUST_UNMUTE,
                AudioManager.FLAG_ALLOW_RINGER_MODES
            )
            audioManager.setStreamVolume(
                AudioManager.STREAM_RING,
                audioManager.getStreamMinVolume(AudioManager.STREAM_RING),
                0
            );
        } catch (e : SecurityException){
            // DND (Don't Disturb Mode) is probably ON, we are not allowed to set the volume
            //  in this scenario, But in this case no incoming call is possible anyway.
        }
    }
    val requestBuilder = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
    requestBuilder.setOnAudioFocusChangeListener { _ ->

        Handler(Looper.getMainLooper()).postDelayed({
            val mode = audioManager.mode
            if(mode == AudioManager.MODE_RINGTONE || mode == AudioManager.MODE_IN_CALL){
                //Ring Ring, do your thing
            }
        }, 100)
    }
    audioManager.requestAudioFocus(requestBuilder.build())
}
Gal Rom
  • 6,221
  • 3
  • 41
  • 33