1

I'm creating an application that sends user position information to the server every 10 minutes. For the purpose of functionality even when the application is in the background or the mobile is locked, I used the Foreground service. When the application is built directly from Visual Studio to the emulator or via USB to a real device, everything works even when the device is locked. The moment the device disconnects or a release build is performed - the Foreground service does not send information when the device is locked. Does anyone have experience with this? Thank you

Shared code:

MainPage.xaml.cs:

if (MainViewModel.WorkingActions.Contains(buttonText))
{
     DependencyService.Get<IBackgroundService>().StartService(1);

     MessagingCenter.Subscribe<Application>(this, "Ping", async (sender) =>
     {
         await PingWorkingToServer();
     });
 }
 else
 {
     DependencyService.Get<IBackgroundService>().StopService();
 }

IBackgroundService.cs

public interface IBackgroundService
{
    // 1 = logged in, 2 = not logged in
    void StartService(int pingType);
    void StopService();
}

Android:

AndroidServiceHelper.cs

internal class AndroidServiceHelper : IBackgroundService
{
    private static readonly Context context = global::Android.App.Application.Context;
    private static readonly Intent intent = new Intent(context, typeof(DependentService));
    private bool isRunning = false;
    // 1 = logged in 2 = not logged in
    public void StartService(int pingType)
    {
        intent.PutExtra("PingType", pingType);
        if(!isRunning)
        {
            if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
            {
                context.StartForegroundService(intent);
            }
            else
            {
                context.StartService(intent);
            }
            isRunning = true;
        }
    }

    public void StopService()
    {
        context.StopService(intent);
        isRunning = false;
    }
}

DependentService.cs

[Service(Enabled = true)]
public class DependentService : Service
{
    private Handler handler;
    private Action runnable;
    private bool isStarted;
    private int DELAY_BETWEEN_MESSAGES;
    private readonly int NOTIFICATION_SERVICE_ID = 1001;
    private readonly int NOTIFICATION_PING_ID = 1002;
    private readonly string NOTIFICATION_CHANNEL_ID = "1003";
    private readonly string NOTIFICATION_CHANNEL_NAME = "MyChannel";
    private const int ONE_MINUTE_MILIS = 60000;
    private int PingType = -1;

    public override void OnCreate()
    {
        base.OnCreate();

        var minutes = Xamarin.Essentials.Preferences.Get("PingTime", 10);

        DELAY_BETWEEN_MESSAGES = minutes * ONE_MINUTE_MILIS;

        handler = new Handler();

        runnable = new Action(() =>
        {
            if (isStarted)
            {
                DispatchNotificationThatPingIsGenerated();
                handler.PostDelayed(runnable, DELAY_BETWEEN_MESSAGES);
            }
        });
    }

    private void DispatchNotificationThatPingIsGenerated()
    {
        var intent = new Intent(this, typeof(MainActivity));
        intent.AddFlags(ActivityFlags.ClearTop);

        Notification.Builder notificationBuilder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
            .SetSmallIcon(Resource.Drawable.dochazka_icon)
            .SetContentTitle("Ukládání pozice")
            .SetContentText("Právě odesílám vaši polohu.")
            .SetAutoCancel(true);

        var notificationManager = (NotificationManager)GetSystemService(NotificationService);
        notificationManager.Notify(NOTIFICATION_PING_ID, notificationBuilder.Build());

        if (PingType == 1)
        {
            MessagingCenter.Send(Xamarin.Forms.Application.Current, "Ping");
        }
        else if (PingType == 2)
        {
            MessagingCenter.Send(Xamarin.Forms.Application.Current, "PingLocally");
        }
    }

    public override IBinder OnBind(Intent intent)
    {
        return null;
    }

    public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
    {
        if (isStarted)
        {
            // service is already started
        }
        else
        {
            PingType = intent.GetIntExtra("PingType", -1);
            CreateNotificationChannel();
            DispatchNotificationThatServiceIsRunning();

            handler.PostDelayed(runnable, DELAY_BETWEEN_MESSAGES);
            isStarted = true;
        }
        return StartCommandResult.Sticky;
    }

    //start a foreground notification to keep alive 
    private void DispatchNotificationThatServiceIsRunning()
    {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
               .SetDefaults((int)NotificationDefaults.All)
               .SetSmallIcon(Resource.Drawable.dochazka_icon)
               .SetSound(null)
               .SetChannelId(NOTIFICATION_CHANNEL_ID)
               .SetPriority(NotificationCompat.PriorityDefault)
               .SetAutoCancel(false)
               .SetContentTitle("Ukládání pozice")
               .SetContentText("Jste v práci, aplikace pravidelně ukládá polohu.")
               .SetOngoing(true);

        StartForeground(NOTIFICATION_SERVICE_ID, builder.Build());
    }

    public override void OnDestroy()
    {
        // Stop the handler.
        handler.RemoveCallbacks(runnable);

        // Remove the notification from the status bar.
        var notificationManager = (NotificationManager)GetSystemService(NotificationService);
        notificationManager.Cancel(NOTIFICATION_SERVICE_ID);

        isStarted = false;
        base.OnDestroy();
    }

    private void CreateNotificationChannel()
    {
        //Notification Channel
        NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationImportance.Max);

        NotificationManager notificationManager = (NotificationManager)GetSystemService(NotificationService);
        notificationManager.CreateNotificationChannel(notificationChannel);
    }
}

AndroidManifest.xml

<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
<application android:label="Docházka lokalita" android:theme="@style/MainTheme" android:icon="@mipmap/launcher_foreground">
    <service android:name=".DependentService" android:foregroundServiceType="location" />
</application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-feature android:name="android.hardware.location" android:required="false" />
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-feature android:name="android.hardware.location.network" android:required="false" />
<queries>
    <intent>
        <action android:name="android.support.customtabs.action.CustomTabsService" />
    </intent>
</queries>
Foro
  • 35
  • 10

1 Answers1

3

You can use wakelock to keep the cpu wakeup state after the screen is off or locked so that the service continues to run. Like:

public Constructor()
{
    PowerManager powerManager = Android.App.Application.Context.GetSystemService(Context.PowerService) as PowerManager;
    _wakeLock = powerManager.NewWakeLock(WakeLockFlags.Partial, "ServiceWakeLock");
}

public override void OnCreate()
{
    base.OnCreate();
    if (_wakeLock != null)
        _wakeLock.Acquire();
}

public override void OnDestroy()
{
    if (_wakeLock != null)
    {
        _wakeLock.Release();
        _wakeLock = null;
    }
    ...
    base.OnDestroy();
}

Adrain
  • 1,946
  • 1
  • 3
  • 7
  • check the doc about the "Keep the CPU on" part – Adrain Mar 16 '22 at 06:31
  • I tried this, but it didn't help, once my phone is locked, service is running in the background (i can see icon of notification), but it doesn't send information to the server. Once i unlock my phone, it works instantly. Anyway, thanks for suggestion. – Foro Mar 16 '22 at 06:44
  • Add some breakpoints or output some messages to see check if service is working. – Adrain Mar 16 '22 at 08:20
  • Ok, i've made some output messages and these doesn't work. So service doesn't run, when phone is in sleep mode. Interestingly, when the service is called every few seconds and not minutes, it works. – Foro Mar 17 '22 at 05:57
  • because android will put every service to sleep after a little while in order to preserve battery – Adrain Mar 17 '22 at 06:50
  • check here https://stackoverflow.com/questions/48903548/background-service-going-to-sleep – Adrain Mar 17 '22 at 06:50
  • Yes, you are definitely right. The problem is that I've tried this solution in recent code modifications that aren't listed here. Now if the service is running, the device is holding a notification that cannot be closed until the service is stopped. At the moment when I to send information to the server, I raise a new notification about sending to the server in the way you have specified. Unfortunately, this only works if this action is always called for a few seconds in a row. (I'll update code in question) – Foro Mar 17 '22 at 07:24
  • Ok, i tried one more time your suggestion with WakeLock and it works. Many thanks. – Foro Mar 17 '22 at 13:42
  • 2
    Just to add, when you are debugging (i.e. with your cable attached), the app never really goes to sleep. You can check this by break pointing your OnSleep method in App.xaml.cs, you should notice it is never called. Not a solution but may explain why you see it working when attached to Visual Studio. – Mansel Apr 20 '22 at 13:48