I am developing an application using .NET MAUI that needs to continuously listen for missed calls and send the caller’s phone number to an API endpoint. I have implemented a foreground service to keep the app running in the background. The service works for a few minutes, but then stops running when the device goes into idle mode or when the app is not opened, resulting in missed API calls.
I have tried using a foreground service with a persistent notification, but it does not seem to keep the service running consistently. Here is my implementation:
I need the service to continuously run in the background and listen for missed calls even when the phone is idle or the app is not opened. I also want to ensure that the necessary permissions are requested and granted, including notification access permissions. What changes or best practices should I follow to achieve this?
`**MainActivity.cs**
using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Provider; // For Settings.Secure
using AndroidX.Core.App;
using AndroidX.Core.Content;
using Android.Widget;
namespace MissedCallSMSService.Platforms.Android
{
[Activity(Label = "MissedCallSMSService", MainLauncher = true)]
public class MainActivity : Activity
{
const int RequestCallLogPermissionId = 0;
const int RequestNotificationAccessId = 1;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Start the MissedCallService
StartMissedCallService();
// Check and request permissions
CheckAndRequestPermissions();
}
private void StartMissedCallService()
{
Intent serviceIntent = new Intent(this, typeof(MissedCallService));
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
StartForegroundService(serviceIntent);
}
else
{
StartService(serviceIntent);
}
}
void CheckAndRequestPermissions()
{
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadPhoneState) != Permission.Granted ||
ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadCallLog) != Permission.Granted)
{
ActivityCompat.RequestPermissions(this, new string[]
{
Manifest.Permission.ReadPhoneState,
Manifest.Permission.ReadCallLog
}, RequestCallLogPermissionId);
}
else
{
CheckAndRequestNotificationPermission();
}
}
void CheckAndRequestNotificationPermission()
{
if (!IsNotificationServiceEnabled())
{
// Guide the user to the notification access settings
Intent intent = new Intent(Settings.ActionNotificationListenerSettings);
StartActivityForResult(intent, RequestNotificationAccessId);
}
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == RequestCallLogPermissionId)
{
if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
{
// Permissions granted
CheckAndRequestNotificationPermission();
}
else
{
// Permissions denied
Toast.MakeText(this, "Permissions denied. The app requires call log access to function properly.", ToastLength.Long).Show();
}
}
}
private bool IsNotificationServiceEnabled()
{
string pkgName = PackageName;
string flat = Settings.Secure.GetString(ContentResolver, "enabled_notification_listeners");
if (flat != null && flat.Contains(pkgName))
{
return true;
}
return false;
}
}
}
**MissedCallService.cs**
using Android;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Util;
using AndroidX.Core.App;
using AndroidX.Core.Content;
using System.Threading;
using System.Threading.Tasks;
namespace MissedCallSMSService.Platforms.Android
{
[Service]
public class MissedCallService : Service
{
private MissedCallReceiver mReceiver;
private const int NotificationId = 1;
private string NOTIFICATION_CHANNEL_ID = "1000";
private int NOTIFICATION_ID = 1;
private string NOTIFICATION_CHANNEL_NAME = "notification";
public override IBinder OnBind(Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
// Run the service in the foreground to ensure it continues running
Notification notification = new NotificationCompat.Builder(this, "missed_call_channel")
.SetContentTitle("Missed Call Service")
.SetContentText("Listening for missed calls")
.SetSmallIcon(Resource.Drawable.maui_splash_image)
.Build();
// StartForeground(NotificationId, notification);
StartForegroundService();
// Start listening for missed calls
ListenForMissedCalls();
return StartCommandResult.Sticky;
}
private void StartForegroundService()
{
var notifcationManager = GetSystemService(Context.NotificationService) as NotificationManager;
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
CreateNotificationChannel(notifcationManager);
}
var notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
notification.SetAutoCancel(false);
notification.SetOngoing(true);
notification.SetSmallIcon(Resource.Mipmap.appicon);
notification.SetContentTitle("ForegroundService");
notification.SetContentText("Foreground Service is running");
StartForeground(NOTIFICATION_ID, notification.Build());
}
private void CreateNotificationChannel(NotificationManager notificationManager)
{
var channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME,
NotificationImportance.High);
notificationManager.CreateNotificationChannel(channel);
}
private void ListenForMissedCalls()
{
mReceiver = new MissedCallReceiver();
var filter = new IntentFilter();
filter.AddAction("android.intent.action.PHONE_STATE");
RegisterReceiver(mReceiver, filter);
// Check periodically if permissions are granted and notify if not
Task.Run(() =>
{
while (true)
{
Thread.Sleep(5000); // Sleep for 5 seconds before checking again
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadPhoneState) != Permission.Granted ||
ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadCallLog) != Permission.Granted)
{
NotifyUserToGrantPermissions();
}
}
});
}
private void NotifyUserToGrantPermissions()
{
NotificationManager notificationManager = (NotificationManager)GetSystemService(NotificationService);
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
var channel = new NotificationChannel("missed_call_channel", "Missed Call Channel", NotificationImportance.High)
{
Description = "Notifications for missed call permissions"
};
notificationManager.CreateNotificationChannel(channel);
}
var notificationIntent = new Intent(this, typeof(MainActivity));
notificationIntent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTask);
var pendingIntent = PendingIntent.GetActivity(this, 0, notificationIntent, PendingIntentFlags.UpdateCurrent);
var builder = new NotificationCompat.Builder(this, "missed_call_channel")
.SetContentTitle("Missed Call Service")
.SetContentText("Please grant phone state and call log permissions.")
.SetSmallIcon(Resource.Drawable.maui_splash_image)
.SetContentIntent(pendingIntent)
.SetAutoCancel(true);
notificationManager.Notify(NotificationId, builder.Build());
}
public override void OnDestroy()
{
base.OnDestroy();
if (mReceiver != null)
{
UnregisterReceiver(mReceiver);
}
}
}
}`
Thofa Technologies is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.