Reacting to incoming phone calls in Xamarin.Android #1

A few years ago – clearly before the smartphone era – I remember owning an old Nokia phone, which had (although being everything else than “smart”) one very handy feature: Whenever one of my contacts called me, the phone would speak out the caller’s name inbetween ringing. Since I’ve been missing that feature ever since, and my current Android device doesn’t seem to support the same thing out of the box, I decided to implement it on my own – not only to be able to use it, but also because this covers several interesting aspects about Xamarin and Android development. Therefore I invite you to follow me while I’m discussing the most important parts of the project!

Let’s start with the most obvious question mark: How to detect an incoming call on the Android platform, using Xamarin? In general, there are two option for approaching this: Using a PhoneStateListener, or registering a BroadcastReceiver that reacts on phone call state changes. I’d like to present one by one in detail, and in the end discuss which approach is more suitable for our needs.

Android’s PhoneStateListener purpose is to inform about changes in all aspects of the telephony system: e.g. phone service, voicemail, signal strength, and (here we go!) phone calls.

The basic structure is simple: Create a class that is derived from Android.Telephony.PhoneStateListener and override that methods needed for your specific tasks. In our case, we’re interested in the OnCallStateChanged method: This one is invoked whenever a new phone call is started (either incoming or outgoing), or when it has ended. Since we are only interested in fresh, incoming calls (not outgoing calls, and not the state goind back to idle after a call), we filter for the Ringing call state:

public class PhoneCallDetector : PhoneStateListener
{
	public override void OnCallStateChanged(CallState state, string incomingNumber)
	{
		base.OnCallStateChanged(state, incomingNumber);
		
		if (state == CallState.Ringing)
		{
			var number = incomingNumber;
			// TODO check number for known contact...
		}
	}
}

Note that for incoming calls, the caller’s number is conveniently passed into this method for further processing – at least if everything is set up correctly. I’ll come back to that in a moment.

With that settled, let’s move on and discuss how to integrate the PhoneCallDetector into an app. Think about the nature of phone calls: They arrive at any time, the only precondition is that the phone is up and running, but our app’s visual activity need not necessarily be shown in foreground. This means, we need to define a service that runs in background, and register the listener in there:

[Service]
public class PhoneCallService : Service
{
	public override void OnCreate()
	{
		base.OnCreate();
	}

	public override IBinder OnBind(Intent intent)
	{
		throw new NotImplementedException();
	}

	public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
	{
		base.OnStartCommand(intent, flags, startId);

		var callDetector = new PhoneCallDetector();
		var tm = (TelephonyManager) base.GetSystemService(TelephonyService);
		tm.Listen(callDetector, PhoneStateListenerFlags.CallState);

		return StartCommandResult.Sticky;
	}
}

As you see, also the service class is a rather simple one. The OnBind method must be overridden although it will never be called in our use case, so we ignore it for the moment. The interesting part is the OnStartCommand method that is invoked when starting the service: Here, we initialize our PhoneCallDetector (which under the hood is a phone state listener), and instruct the system’s telephony service that it is interested in call state changes. From now, on (as long as the service runs) the PhoneCallDetector instance will be notified about phone calls.

Finally, we’re ready to run our service. For now (to be able to easily test it), simply create an otherwise empty Xamarin.Android app that does nothing else than
– requesting the ReadPhoneState permission (I’ll explain this in detail later), and
– starting our PhoneCallService.

For this simple test, I only show those parts of the app’s main activity that reflect the simplest possible permission request and service startup workflow using the Xamarin.Essentials Nuget package, without user information, error handling etc.:

public class MainActivity : AppCompatActivity
{
	// ...

	protected override void OnCreate(Bundle savedInstanceState)
	{
		// ...
		
		var permissions = new string[]
		{
			Manifest.Permission.ReadPhoneState
		};
		ActivityCompat.RequestPermissions(this, permissions, 123);
	}
}

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
	base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
	
	if (requestCode == 123 && grantResults.Length > 0 && grantResults[0] == Permission.Granted)
	{
		Intent serviceStart = new Intent(this, typeof(PhoneCallService));
		this.StartService(serviceStart);
	}
}

Obviously, this is not a stable solution for starting a service in a production environment, but it’s the easiest way of testing our service. To do so, insert a breakpoint in the PhoneCallDetector class as indicated in the code snipped shown above, start the app, and call yourself!

As promised, an additional comment about permissions: The whole phone state listening workflow will probably also work without requesting any permissions, in my experience on incoming calls the caller’s phone number will not be provided (remember the incomingNumber parameter in the PhoneCallDetector class). So, if you are unable to read the caller’s number, re-check if the ReadPhoneState permission is set!

An alternative approach for detecting phone calls is using a BroadcastReceiver. I will discuss that shortly in a seperate blog post – stay tuned!