3

I don't mind rooting a device as the application will only be used privately, i'm working on a project that requires to monitor the call state of a device, have read the documentation on it

https://developer.android.com/reference/android/telecom/Call.html

and i have been using it but i'm having issue knowing when a call is picked, have checked the documentation and stackoverflow, have realized is a known issue from google itself.

Detecting outgoing call answered on Android

In rooted device detect if an outgoing call has been answered

and many others that i have tried. i understand there is no documented method to achieve this, i'm sure this will be possible because android itself calculate the time spent on a call and also some applications too like TRUE CALLER and some other private app monitor the time spent which is based on when a call is picked and when they hang-up from the call. Have tried a lot myself before deciding to post this, any suggestion on how to achieve this on a ROOTED device.

sodiqOladeni
  • 898
  • 7
  • 14

2 Answers2

1

This is an example of Telephony broadcast receiver which listens to voice calls.

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Environment;
import android.support.v4.app.NotificationCompat;
import android.telephony.TelephonyManager;
import android.util.Log;

import com.nitesh.brill.saleslines.Common_Files.SaveData;
import com.nitesh.brill.saleslines.R;

public class MyPhoneReceiver extends BroadcastReceiver {

    private String phoneNumber;
    Context context;

    @Override
    public void onReceive(final Context context, Intent intent) {
        this.context = context;

        phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
        String extraState = intent.getStringExtra(TelephonyManager.EXTRA_STATE);


            try {

                if (extraState != null) {
                    if (extraState.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {

                        Log.e("State","Offhook");

                    } else if (extraState
                            .equals(TelephonyManager.EXTRA_STATE_IDLE)) {

                        Log.e("State","Idle");


                    } else if (extraState
                            .equals(TelephonyManager.EXTRA_STATE_RINGING)) {
                        if (phoneNumber == null)
                            phoneNumber = intent
                                    .getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);

                        Log.e("State","Ringing");

                    }
                } else if (phoneNumber != null) {
                    Log.e("Outgoing call",""+phoneNumber);



                }
            } catch (Exception e) {
                Log.e(Constants.TAG, "Exception");
                e.printStackTrace();
            }
    }


}

Add this code to your manifest file

<receiver android:name=".MyPhoneReceiver">
            <intent-filter>

                <!-- Intent filters for broadcast receiver -->
                <action android:name="android.intent.action.PHONE_STATE" />
                <action android:name="android.intent.action.NEW_OUTGOING_CALL" />


            </intent-filter>
        </receiver>
Paras Watts
  • 2,565
  • 4
  • 21
  • 46
  • The above code doesn't specify when the call is picked, extending PhoneStateListener will give me all that without rooting my phone, i needed to know when the call is picked(answered) or accepted not when it is ringing.@Paras Watts – sodiqOladeni Mar 22 '18 at 12:16
  • The extra state offhook is called when call has been picked – Paras Watts Mar 22 '18 at 12:17
  • I still dont understand why people are marking this as the correct answer, have implemented this as specified by adding a toast to the offhook state but nothing happen. If the answer is correct can you post a screenshot of a toast message when the call is picked for clarification. @Paras Watts – sodiqOladeni Mar 22 '18 at 12:33
  • In addition to my comment, offhook is fired immediately the call is launched, if this works fine in your side, can you post your device details here, or is it rooted or non rooted device @Paras Watts – sodiqOladeni Mar 22 '18 at 12:39
  • I have tested on both the devices rooted and non-rooted , I use this code to do call recording in my app. – Paras Watts Mar 22 '18 at 12:40
  • Is the app private or for general use, if it for general, i will like to have a link to test it – sodiqOladeni Mar 22 '18 at 12:43
  • E/State: Offhook E/State: Idle E/State: Offhook E/State: Idle E/State: Offhook E/State: Idle E/State: Offhook E/State: Idle E/State: Offhook E/State: Idle E/State: Offhook E/State: Idle – sodiqOladeni Mar 22 '18 at 12:45
  • It is a private app – Paras Watts Mar 22 '18 at 12:47
  • if there is a way you detect ansewered/accepted call and not outgoing call, can we have a private chat on this probably there is something i'm doing wrong, you could help me. @Paras Watts – sodiqOladeni Mar 22 '18 at 12:50
  • `EXTRA_STATE_OFFHOOK` is called when call is being placed, not when call is picked by other user. – global_warming Mar 22 '18 at 12:55
  • Exactly @global_warming but i dont know why people are marking the answer, the issue is a known issue and it can only work on a rooted device based on my research, i just need to put me through it on a rooted device – sodiqOladeni Mar 22 '18 at 13:01
  • You can save the ringing state, if previous state was ringing and current state is idle then call is rejected and if previous state was ringing and current state is offhook then the call is answered – Paras Watts Mar 22 '18 at 13:13
  • Ringing state is not even called atall, it is just offhook (which means the call has been launched) and Idle (which means no outgoing or incoming call), you can help with the code if you are sure of your implementation. @Paras Watts – sodiqOladeni Mar 22 '18 at 13:18
  • @Paras Watts `RINGING` state is for incoming calls. – global_warming Mar 22 '18 at 13:18
  • My bad I think OFFHOOK is called as soon as outgoing call is placed. But in my case which is call recording this code works fine. – Paras Watts Mar 22 '18 at 13:19
  • Yes. You can record. Though if call is ended without ringing on other side, still it will be recorded. – global_warming Mar 22 '18 at 13:24
  • Even the recording will still have empty state recorded because offhook is fired before the ringing start, please anybody that knows who can answer this should help me with it. @Paras – sodiqOladeni Mar 22 '18 at 13:53
1

After going through system logs during call and looking at source code this and this in, I found that PRECISE CALL STATE is something which can be used to listen precise changes during call.

But as you can see most of the things are hidden from documentation using @hide annotation.

When applied to a package, class, method or field, @hide removes that node and all of its children from the documentation.

Though methods and classes are hidden but they can be accessed using Java Reflection API, so I thought giving it a try. But the developer community is so large that most of the things come to your mind can already be found on Google.

So after some Google searches, I found this blog, which explains how to listen to precise call state using Java Reflection API. So I have took this code in its original form its source.

Add this in AndroidManifest.xml file to declare Broadcast Receiver.

<receiver
    android:name=".OutCallLogger"
    android:enabled="true"
    android:exported="true" >
        <intent-filter>
            <action android:name="android.intent.action.PRECISE_CALL_STATE" />
            <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
        </intent-filter>
</receiver>

Permissions required:

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

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

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

Also add this line to manifest for using feature android.hardware.telephony

 <uses-feature android:name="android.hardware.telephony"></uses-feature>

And this is your broadcast receiver class which will be used for getting precise call state for outgoing calls.

public class OutCallLogger extends BroadcastReceiver {

public OutCallLogger() {

}

TelephonyManager Tm;
ITelephony telephonyService;
Class c = null;
Method methodGetInstance = null;
Method methodGetActiveFgCallState=null;
String TAG="Tag";
Object objectCallManager=null;
Context context1;
Class<?> classCallManager;

Class telephonyClass;
Class telephonyStubClass;
Class serviceManagerClass;
Class serviceManagerStubClass;
Class serviceManagerNativeClass;
Class serviceManagerNativeStubClass;

Method telephonyCall;
Method telephonyEndCall;
Method telephonyAnswerCall;
Method getDefault;

Method[] temps;
Constructor[] serviceManagerConstructor;

// Method getService;
Object telephonyObject;
Object serviceManagerObject;
private Timer timer= null;

@Override
public void onReceive(Context context, Intent intent) {
    // TODO: This method is called when the BroadcastReceiver is receiving
    // an Intent broadcast.



    this.context1= context;
    Tm=(TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);

    final ClassLoader classLoader = this.getClass().getClassLoader();
    try {
        classCallManager = classLoader.loadClass("com.android.internal.telephony.CallManager");
        Log.e(TAG, "CallManager: Class loaded " + classCallManager.toString());
        methodGetInstance = classCallManager.getDeclaredMethod("getInstance");
        methodGetInstance.setAccessible(true);
        Log.e(TAG, "CallManager: Method loaded " + methodGetInstance.getName());
        objectCallManager = methodGetInstance.invoke(null);
        Log.e(TAG, "CallManager: Object loaded " + objectCallManager.getClass().getName());
        Method[] aClassMethods = classCallManager.getDeclaredMethods();
        for(Method m : aClassMethods)
        {
            Log.e("MEthods", m.getName());
        }
        methodGetActiveFgCallState = classCallManager.getDeclaredMethod("getActiveFgCallState");
        Log.e(TAG, "CallManager: Method loaded " + methodGetActiveFgCallState.getName());

        Log.e(TAG, "CallManager: What is the Call state = " + methodGetActiveFgCallState.invoke(objectCallManager));
    }
    catch (ClassNotFoundException e) {
        Log.e(TAG, e.getClass().getName() + e.toString());
    }
    catch (NoSuchMethodException e) {
        Log.e(TAG, e.getClass().getName() + e.toString());
    }
    catch (InvocationTargetException e) {
        Log.e(TAG, e.getClass().getName() + e.toString());
    }
    catch (IllegalAccessException e) {
        Log.e(TAG, e.getClass().getName() + e.toString());
    }
    Tm.listen(new PhoneStateListener(){
        public void  onCallStateChanged(int state,String number) {
            super.onCallStateChanged(state, number);

            try {
                if (methodGetActiveFgCallState.invoke(objectCallManager).toString().toLowerCase() .equals("idle"))
                {
                    //Toast.makeText(context1, "I am in idle state", Toast.LENGTH_LONG).show();            }
                    if (methodGetActiveFgCallState.invoke(objectCallManager).toString().toLowerCase() .equals("active"))
                    {
                        //Toast.makeText(context1, "I am in active state", Toast.LENGTH_LONG).show();            }

                        Toast.makeText(context1, " "+methodGetActiveFgCallState.invoke(objectCallManager).toString(), Toast.LENGTH_LONG).show();


                    } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block            e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block            e.printStackTrace();
                } catch (InvocationTargetException e) {
                    // TODO Auto-generated catch block            e.printStackTrace();
                }

                }

            }, PhoneStateListener.LISTEN_CALL_STATE);

        }

A Toast will appear which will tell you about call state.

Since, you have pointed out you don't mind rooting your device, you have to install the generated apk as system app. Simply copy the generated apk to /system/app directory and restart device.

Disclaimer: I have not tested above code, as I don't have a rooted device at the moment.

global_warming
  • 833
  • 1
  • 7
  • 11