I'm trying to detect the state of an outgoing call when it starts playing ringback tone. I have tried various approaches for detecting this state. Here are some of them:
PhoneStateListener
:(Cannot detect when outgoing call is answered in Android)
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
public class CustomPhoneStateListener extends PhoneStateListener {
public void onCallStateChanged(int state, String num) {
Log.d(CallStatusPlugin.TAG, ">>>state changed" + state);
}
}
But states like TelephonyManager.CALL_STATE_IDLE
, TelephonyManager.CALL_STATE_OFFHOOK
doesn't give us those states.
READ_PRECISE_PHONE_STATE
:Adding the method to the above same phone state listener
public void onPreciseCallStateChanged() {
Log.d(CallStatusPlugin.TAG, "onPreciseCallStateChanged");
}
But as per my research, reading the precise state requires the app must be a system app.
NotificationListener
:public class CustomNotificationListener extends NotificationListenerService {
public static final String TAG = "CallStatusPlugin";
public CustomNotificationListener() {
Log.v(TAG, ">>> CustomNotificationListener");
}
public void onNotificationPosted(StatusBarNotification sbn) {
Log.i(TAG, "New Notification");
Bundle extras = sbn.getNotification().extras;
if ("Ongoing call".equals(extras.getString(Notification.EXTRA_TEXT))) {
Log.v(TAG, "outgoing call");
} else if ("Dialing".equals(extras.getString(Notification.EXTRA_TEXT))) {
Log.v(TAG, "dialling call");
}
}
}
But this doesn't help because the OS doesn't change the notification when the ringback tone starts playing for an outgoing call.
BroadcastReceiver
:public class CallBroadcastReceiver extends BroadcastReceiver {
public static final String TAG = "CallStatusPlugin";
public void onReceive(Context context, Intent intent) {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
Log.i(TAG, "CallBroadcastReceiver state: " + state);
// TelephonyManager.EXTRA_FOREGROUND_CALL_STATE = "foreground_state"
Log.d(TAG, "new state >>>>" + intent.getIntExtra("foreground_state", -2));
}
}
But didn't help either.
I also tried getting the instance of default dialer using reflection without success:
//com.samsung.android.incallui
Reflections reflections = new Reflections("com.samsung.android.contacts", new SubTypesScanner(false));
final ClassLoader classLoader = this.getClass().getClassLoader();
ClassLoader[] loaders = { classLoader };
ConfigurationBuilder configurationBuilder = (ConfigurationBuilder) reflections.getConfiguration();
configurationBuilder.setClassLoaders(loaders);
Log.d(TAG, "cl" + classLoader.toString());
Set<Class<? extends Object>> allClasses = reflections.getSubTypesOf(Object.class);
Log.d(TAG, "allclasses" + allClasses.toString());
I could not get any class (maybe I'm not using the reflection properly)
InCallService
:By replacing a default dialer (which I don't want to use) with the custom dialer to get the Call
states.
import android.os.Bundle;
import android.telecom.Call;
import android.telecom.InCallService;
import android.util.Log;
public class CustomInCallService extends InCallService {
public static final String TAG = "CallStatusPlugin";
@Override
public void onCallAdded(Call call) {
Log.d(TAG, "onCallAdded: " + call.getState());
call.registerCallback(
new Call.Callback() {
@Override
public void onConnectionEvent (Call call, String event, Bundle extras) {
Log.d(TAG, "Call.Callback.onConnectionEvent: " + event + ", " + call.getState());
}
@Override
public void onStateChanged (Call call, int state) {
Log.d(TAG, "Call.Callback.onStateChanged: " + state + ", " + call.getState());
}
}
);
}
}
The onStateChanged
finally gives me something as:
Call State -> 9 (STATE_CONNECTING
) -> 1 (STATE_DIALING
) -> 4 (STATE_ACTIVE
)(when answered) -> 7 (STATE_DISCONNECTING
)
But the call state is also changed to STATE_DIALING
when there is some problem with the outgoing call like the dialed number is unreachable or the phone is switched off. So this means we can't say DIALING
state is the state where the outgoing call starts playing the ringback tone.
CallManager
from reflection:(Added 3rd Aug 2018)
public class OutCallLogger extends BroadcastReceiver {
public static final String TAG = "CallStatusPlugin";
public OutCallLogger() {
Log.e(TAG, "\n\n\nOutCallLogger Instance Created\n\n\n");
}
@Override
public void onReceive(Context context, Intent intent) {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
Log.i(TAG, "OutCallLogger state: " + state);
String number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Log.i(TAG, "Outgoing Number: " + number);
// TelephonyManager.EXTRA_FOREGROUND_CALL_STATE = "foreground_state"
Log.d(TAG, "new state >>>>" + intent.getIntExtra("foreground_state", -2));
Log.d(TAG, "new state >>>>" + intent.getIntExtra("ringing_state", -2));
Log.d(TAG, "new state >>>>" + intent.getIntExtra("background_state", -2));
Log.d(TAG, "new state >>>>" + intent.getIntExtra("disconnect_cause", -2));
final ClassLoader classLoader = this.getClass().getClassLoader();
try {
Class<?> callManagerClass = classLoader.loadClass("com.android.internal.telephony.CallManager");
Log.e(TAG, "CallManager: Class loaded " + callManagerClass.toString());
Method[] methods = callManagerClass.getDeclaredMethods();
for (Method m : methods) {
Log.e(TAG, "Methods: " + m.getName());
}
Method getInstanceMethod = callManagerClass.getDeclaredMethod("getInstance");
getInstanceMethod.setAccessible(true);
Log.e(TAG, "CallManager: Method loaded " + getInstanceMethod.getName());
Object callManagerObject = getInstanceMethod.invoke(null);
Log.e(TAG, "CallManager: Object loaded " + callManagerObject.getClass().getName());
Method getAllPhonesMethod = callManagerClass.getDeclaredMethod("getAllPhones");
Log.e(TAG, "CallManager: Method loaded " + getAllPhonesMethod.getName());
Method getForegroundCallsMethod = callManagerClass.getDeclaredMethod("getForegroundCalls");
Log.e(TAG, "CallManager: Method loaded " + getForegroundCallsMethod.getName());
List foregroundCalls = (List) getForegroundCallsMethod.invoke(callManagerObject);
Log.e(TAG, "Foreground calls: " + foregroundCalls + ", " + foregroundCalls.size());
Method getBackgroundCallsMethod = callManagerClass.getDeclaredMethod("getBackgroundCalls");
Log.e(TAG, "CallManager: Method loaded " + getForegroundCallsMethod.getName());
List backgroundCalls = (List) getBackgroundCallsMethod.invoke(callManagerObject);
Log.e(TAG, "Background calls: " + backgroundCalls + ", " + backgroundCalls.size());
Timer timer = new Timer();
// keep printing all the for 20 seconds to check if we got one
TimerTask doAsynchronousTask = new TimerTask() {
long t0 = System.currentTimeMillis();
@Override
public void run() {
// cancel the timer after 20 seconds
if (System.currentTimeMillis() - t0 > 20 * 1000) {
cancel();
return;
}
try {
List phonesObject = (List) getAllPhonesMethod.invoke(callManagerObject);
Log.e(TAG, "All phones " + phonesObject + ", " + phonesObject.size());
List foregroundCalls = (List) getForegroundCallsMethod.invoke(callManagerObject);
Log.e(TAG, "Foreground calls: " + foregroundCalls + ", " + foregroundCalls.size());
Object backgroundCalls = getBackgroundCallsMethod.invoke(callManagerObject);
Log.e(TAG, "Background calls: " + backgroundCalls);
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
Log.i(TAG, "New state: " + state);
} catch (Exception e) {
Log.e(TAG, ">>>1. " + e.getMessage());
}
}
};
timer.schedule(doAsynchronousTask, 0, 1000); //execute in every 1000 ms
} catch (ClassNotFoundException e) {
Log.e(TAG, ">>>2. " + e.getMessage());
} catch (NoSuchMethodException e) {
Log.e(TAG, ">>>3. " + e.getMessage());
} catch (InvocationTargetException e) {
Log.e(TAG, ">>>4. " + e.getMessage());
} catch (IllegalAccessException e) {
Log.e(TAG, ">>>5. " + e.getMessage());
}
}
}
But I'm getting all the background/foreground calls & phones as emty results:
08-03 15:19:22.638 2586 4636 E CallStatusPlugin: All phones [], 0
08-03 15:19:22.639 2586 4636 E CallStatusPlugin: Foreground calls: [], 0
08-03 15:19:22.639 2586 4636 E CallStatusPlugin: Background calls: []
Not sure if OS is using the CallManager
or not.
After 2-3 weeks of research, I learned:
Ringing sound of an outgoing call == Ringback tone
State dialing !== State of playing ringback tone
I kept debugging by reading all the OS logs by adb logcat '*:V'
and saw that a log is being printed at the time when I hear the ringback tone (adb logcat -b system '*:V'
):
07-31 13:34:13.487 3738 29960 I Telephony: AsyncConnectTonePlayer : play
07-31 13:34:13.784 3273 7999 D SSRM:p : SIOP:: AP = 330, PST = 313 (W:26), BAT = 294, USB = 0, CHG = 0
07-31 13:34:13.902 3738 29960 I Telephony: AsyncConnectTonePlayer : onCompletion
07-31 13:34:14.304 3273 15438 D CustomFrequencyManagerService: releaseDVFSLockLocked : Getting Lock type frm List : DVFS_MIN_LIMIT frequency : 1352000 uid : 1000 pid : 3273 tag : com.samsung.android.incallui@2
07-31 13:34:14.639 3273 7999 D AudioService: getStreamVolume 0 index 10
07-31 13:34:16.449 4175 4175 D io_stats: !@ 179,0 r 264349 9232882 w 465999 10111332 d 42634 3418440 f 235485 235449 iot 468730 459107 th 51200 0 0 pt 0 inp 0 0 104823.814
Then I started googling for something related to io stats events android & gsm connection events but I'm not able to figure out any way to get that particular state of an outgoing call where the call starts playing ringback tone.
I looked at various Android codes to find out some hints:
TelephonyConnection
GsmConnection
Any hints or direction where I should look to achieve this? Like GSM connection, ConnectionService
, ConnectivityManager
, ConnectionService
or ConnectionRequest
anything?
Reading more logs led me to SipManager and I read about SIP Basic Call Flow Examples which I think, Android uses for making or receiving the calls.
07-31 16:29:55.335 4076 5081 I reSIProcate: INFO | RESIP:TRANSACTION | TuSelector.cxx:131 | Start add(alm)
07-31 16:29:55.335 4076 5081 I reSIProcate: INFO | RESIP:TRANSACTION | TuSelector.cxx:138 | AlarmMsg is not null
07-31 16:29:55.335 4076 5081 I reSIProcate: INFO | RESIP:TRANSACTION | TuSelector.cxx:140 | Sending AlarmMessage 0 to TUs
07-31 16:29:55.335 4076 5081 I reSIProcate: INFO | RESIP:TRANSACTION | TuSelector.cxx:145 | End add(alm)
07-31 16:29:55.335 4076 5081 I reSIProcate: INFO | RESIP:DNS | DnsResult.cxx:240 | Whitelisting 2405:200:380:1581::42(28): 2405:200:380:1581::42
07-31 16:29:55.335 4076 5081 I reSIProcate: INFO | RESIP:DNS | RRVip.cxx:128 | updating an existing vip: 2405:200:380:1581::42 with 2405:200:380:1581::42
07-31 16:29:55.335 4076 5081 I reSIProcate: INFO | RESIP:TRANSACTION | TuSelector.cxx:71 | Send to TU: TU: CALL-SESSION(8) size=0
07-31 16:29:55.335 4076 5081 I reSIProcate:
07-31 16:29:55.335 4076 5081 I reSIProcate: SipResp: 180 tid=935e2afa889bdcad cseq=1 INVITE [email protected]:5070 / 1 from(wire)
07-31 16:29:55.336 4076 5081 D StackIF : readMessage: messageType 2 tid 0 pduLength 920
07-31 16:29:55.336 4076 5081 D SECIMSJ[0]: [UNSL]< NOTIFY_SIP_MESSAGE
07-31 16:29:55.336 4076 5081 D StackIF[0]: processNotify: id NOTIFY_SIP_MESSAGE
07-31 16:29:55.340 4076 5081 D SIPMSG[0]: [<--] SIP/2.0 180 Ringing [CSeq: 1 INVITE]
07-31 16:29:55.340 4076 4896 D ResipRawSipHandler: handleMessage: event: 100
07-31 16:29:55.340 4076 5082 D OpenApiServiceModule: handleMessage: what 100
07-31 16:29:55.341 4076 4896 D ResipVolteHandler: handleMessage: evt 114
07-31 16:29:55.341 4076 5081 D CpAudioEngineClient: SAE_NotiInfo: SAE_NotiInfo string: 16:29:55.341<180 Ringing:1 INVITE
07-31 16:29:55.341 4076 5081 D VoIpEngineProxy: NotiInfo:Sending IPC_IMS_INFO Noti
Will that be of use?
There are various reasons why your Android phone won't ring when someone calls, including: Your ringer volume is turned down. Your phone is on Do Not Disturb or Airplane mode. You turned on call forwarding.
Go to Settings > Sounds (or Settings > Sounds & Haptics), and drag the Ringer and Alerts slider back and forth a few times. If you don't hear any sound, or if your speaker button on the Ringer and Alerts slider is dimmed, your speaker might need service.
Now in Android R, we have the permission for public api.
READ_PRECISE_PHONE_STATE
Allows read only access to precise phone state. Allows reading of detailed information about phone state for special-use applications such as dialers, carrier applications, or ims applications.
Maybe you can use it right now for the precise phone call state,I have got the precise phone call state in custom Android system.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With