5

I create background service with is started after user signed in and should work even after activity was 'throw to trash'. Service show notifications that it's running ,register two broadcast receivers for monitoring wifi and phone state and should keep running until token isn't expired or user logout.

Everything works but service is killed by android.The only solution that REALLY WORKS is instruction from this article http://nine-faq.9folders.com/articles/37422-stop-your-huawei-smartphone-from-closing-apps-when-you-lock-the-screen Unfortunate this solution isn't acceptable because it's dependence on user.

Code created by C# using Xamarin , but If any one know how to implement solution from article programically I'll be glad for useful advice even in other languages (java ,kotlin)

Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
          android:versionCode="1" 
          android:versionName="1.0" 
          package="com.companyname.com.bgapptest">
  <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="28" />
  <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
    <service
       android:name="com.BGAppTest.BackgroundService"
       android:enabled="true"
       android:exported="false"/>
  </application>
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.CALL_PHONE" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  <uses-permission android:name="android.permission.WAKE_LOCK" />
  <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
</manifest>

Start service from activity

Activity

    using System;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Provider;
using Android.Runtime;
using Android.Support.Design.Widget;
using Android.Support.V7.App;
using Android.Views;
using Android.Widget;
using AlertDialog = Android.Support.V7.App.AlertDialog;

namespace com.BGAppTest
{
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        public static string HOPE = "Nothing";
        string[] perms = new string[] { "android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_NETWORK_STATE", "android.permission.READ_PHONE_STATE","android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"};

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            SetContentView(Resource.Layout.activity_main);
            Android.Support.V7.Widget.Toolbar toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
            SetSupportActionBar(toolbar);
            FloatingActionButton fab = FindViewById<FloatingActionButton>(Resource.Id.fab);
            fab.Click += FabOnClick;
            HOPE = DateTime.Now.ToString();
            StartService();
        }

        public override bool OnCreateOptionsMenu(IMenu menu)
        {
            MenuInflater.Inflate(Resource.Menu.menu_main, menu);
            return true;
        }

        public override bool OnOptionsItemSelected(IMenuItem item)
        {
            int id = item.ItemId;
            if (id == Resource.Id.action_settings)
            {
                return true;
            }

            return base.OnOptionsItemSelected(item);
        }

        private void FabOnClick(object sender, EventArgs eventArgs)
        {
            View view = (View)sender;
            Intent service = new Intent(this, typeof(BackgroundService));
            StopService(service);
        }

        protected void IGnoreBatteryActivity()
        {
            PowerManager m = GetSystemService(PowerService) as PowerManager;
            Intent intent = new Intent();
            if (m.IsIgnoringBatteryOptimizations(this.PackageName))
            {
                //intent.SetAction(Settings.ActionIgnoreBatteryOptimizationSettings);
            }
            else
            {
                intent.SetAction(Settings.ActionRequestIgnoreBatteryOptimizations);
                intent.SetData(Android.Net.Uri.Parse("package:" + PackageName));
                StartActivity(intent);

            }

        }

        void StartService()
        {
            foreach (var p in perms)
            {
                if (CheckSelfPermission(p) == Permission.Denied)
                {
                    RequestPermissions(perms, 2);
                    return;
                }
            }
            IGnoreBatteryActivity();
            Intent service = new Intent(this, typeof(BackgroundService));
            if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
                StartForegroundService(service);
            else
                StartService(service);

        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
        {
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            if (grantResults.Any(x => x == Permission.Denied))
            {
                RunOnUiThread(() =>
                {
                    new AlertDialog.Builder(this)
                       .SetMessage("Uprawnienia są wymagane.Chcesz nadać uprawnienia?")
                       .SetNegativeButton("Nie", delegate
                       {
                           this.FinishAffinity();
                       })
                       .SetPositiveButton("Tak", delegate
                       {
                           RequestPermissions(perms, requestCode);
                       })
                       .SetCancelable(false)
                       .Create()
                       .Show();
                });
            }
            else
                StartService();
        }

    }
}

Service

    [Service(Name = "com.BGAppTest.BackgroundService")]
public class BackgroundService : Service
{
    NetworkChangeReceiver networkReceiver;
    PhoneCallsReceiver receiver;
    const int Service_Running_Notification_ID = 937;
    public bool isStarted = false;
    PowerManager.WakeLock wakeLock;
    public BackgroundService()
    {

    }
    [return: GeneratedEnum]
    public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
    {
        if (isStarted)
            return StartCommandResult.Sticky;
        isStarted = true;
        PowerManager m = GetSystemService(Context.PowerService) as PowerManager;
        wakeLock = m.NewWakeLock(WakeLockFlags.Partial, "MYWeakLock");
        wakeLock.Acquire();
        return StartCommandResult.Sticky;
    }
    public override void OnCreate()
    {
        base.OnCreate();
        RegisterForegroundService();
        RegisterWifiReceiver();
        RegisterPhoneReceiver();

    }



    public void RegisterForegroundService()
    {
        Notification notification = BuildNotification("Title","Text");
        StartForeground(Service_Running_Notification_ID, notification);
    }


    Notification BuildNotification(string title, string message)
    {
        NotificationCompat.Builder notificationBuilder = null;
        if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
        {
            String NOTIFICATION_CHANNEL_ID = "com.BGAppTest";
            NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "MY_Foreground", NotificationImportance.High);
            NotificationManager manager = (NotificationManager)GetSystemService(Context.NotificationService);
            manager.CreateNotificationChannel(chan);
            notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
        }
        else
            notificationBuilder = new NotificationCompat.Builder(this);

        return notificationBuilder
          .SetSmallIcon(Resource.Drawable.ic_mtrl_chip_checked_black)
           .SetContentTitle(Resources.GetString(Resource.String.app_name))
           .SetContentIntent(BuildIntentToShowMainActivity())
           .SetContentTitle(title)
           .SetStyle(new NotificationCompat.BigTextStyle().BigText(message))
           .SetOngoing(true)
           .Build();

    }

    private PendingIntent BuildIntentToShowMainActivity()
    {
        var intent = this.PackageManager.GetLaunchIntentForPackage(this.PackageName);
        intent.AddFlags(ActivityFlags.ClearTop);
        var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.UpdateCurrent);
        return pendingIntent;

    }


    public void RegisterWifiReceiver()
    {
        var networkReceiver = new NetworkChangeReceiver();
        IntentFilter intentFilters = new IntentFilter();
        intentFilters.AddAction("android.net.conn.CONNECTIVITY_CHANGE");
        RegisterReceiver(networkReceiver, intentFilters);
    }
    public void RegisterPhoneReceiver()
    {
        var receiver = new PhoneCallsReceiver();
        IntentFilter intentFilters = new IntentFilter();
        intentFilters.AddAction("android.intent.action.PHONE_STATE");
        RegisterReceiver(receiver, intentFilters);

    }

    public override IBinder OnBind(Intent intent)
    {
        return null;
    }
    #region Session checker
    //TODO:przenieść tą fukcionalność do odzielnego serwisu
    System.Timers.Timer timers = new System.Timers.Timer();
    private void StartTokenExpiredTimer()
    {
    }

    #endregion
    public override void OnDestroy()
    {
        base.OnDestroy();
        try
        {
            UnregisterReceiver(receiver);
            UnregisterReceiver(networkReceiver);
        }
        catch { }

    }
}

Receivers

using Android.Content;
using Android.Widget;


namespace com.BGAppTestReceivers
{
    public class NetworkChangeReceiver: BroadcastReceiver
    {

        static NetworkChangeReceiver()
        {

        }
        public NetworkChangeReceiver()
        {
        }
        public override async void OnReceive(Context context, Intent intent)
        {
            Toast.MakeText(context, "BGNetworkChange", ToastLength.Short).Show();

        }


    }

    public class PhoneCallsReceiver : BroadcastReceiver
    {

        static PhoneCallsReceiver()
        {

        }
        public PhoneCallsReceiver()
        {
        }
        public override async void OnReceive(Context context, Intent intent)
        {
            Toast.MakeText(context, "BGPhoneChange", ToastLength.Short).Show();
        }

    }


}

UPDATE 26.09.2020

I add full code of my example application, made it as simple as I could

UPDATE 30.09.2020 Add android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS and ActionRequestIgnoreBatteryOptimizations

DespeiL
  • 993
  • 9
  • 28
  • If you get an exception the service will stop. You need to wrap code in an exception handler to prevent code from stopping. – jdweng Sep 14 '20 at 11:08
  • I believe that there is no erors because when I manual turn off automatic battery management for application on the device everything work fine – DespeiL Sep 14 '20 at 11:14
  • 1
    It seems an expected result which is caused by the design of HUAWEI system (EMUI) . You could firstly test your app on Google Pixel or emulator . – Lucas Zhang Sep 14 '20 at 11:16
  • 1
    That is a bad assumption. If there was no errors than the application would continue to run. You need to review you GEOMETRY notes on valid assumptions. So Theorem : When I leave battery management off, I do not get any errors. Contrapositive : I get errors when I turn battery management on. – jdweng Sep 14 '20 at 11:25
  • Ok , since you assume that code is alright I'll try to wrap code in an exception handler and than we will see =) – DespeiL Sep 14 '20 at 11:38
  • 1
    @jdweng , after 1h aplication died again. I put handler on each method of service and broadcast receivers – DespeiL Sep 14 '20 at 13:08
  • See following : https://learn.microsoft.com/en-us/appcenter/sdk/crashes/xamarin – jdweng Sep 14 '20 at 15:38
  • @jdweng , I update my question. There is 100% no error and everything works if I set settings as article says – DespeiL Sep 30 '20 at 13:49
  • Did my link on crashes help solve issue? – jdweng Sep 30 '20 at 14:19

2 Answers2

4

I'm author of Floating Apps and with 7 years of development, I've seen running it on almost 11.000 different devices. I've also posted a series of articles on background services and floating windows.

First to say, it's impossible to achieve this on all devices without user interaction. Some vendors change Android system too much and add aggresive memory or process management and manual action is sometimes necessary. Also, there are devices on which this cannot be solved at all.

What helps is to run the service in different process (using multiprocess in AndroidManifest.xml) and you should add android:stopWithTask for the service so it's not being killed with the activity - helps on some devices.

But basically, in Floating Apps, I try to detect phones with known issues and ask users to set their phone for the app to work correctly - disable battery optimisations, add to protected apps on Huawei, etc. Also, a lot of issues is solved by our customer support (my wife on maternity leave actually :-)).

The last instance is to send users to: https://dontkillmyapp.com

Frankly, for the most of users, this is actually not a big deal and with disabled battery optimisations, the app works quite well for the most of them. However, for a part of them, there is no solution at all - not programatic nor manual. Don't try to solve this problem generally for all phones.

Václav Hodek
  • 638
  • 4
  • 9
3

You must request permission to exempt your app from battery saving and App standby mode.

You can try it manually at first from Settings > Apps > Special App Access > Battery Optimisation > Turn optimization off for your app.

If it works for you then you can ask the user to whitelist your app using the following permission

android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
  • Yes as I mentioned before manually turning optimization off works fine for me. Thx for your answer , I will try this solution and let you know how it goes – DespeiL Sep 27 '20 at 15:21
  • @YousseShamass unfortunately this permission didn't helped. It works but not helping. Do you have any suggestion how to implement pragmatically working solution that I described? – DespeiL Sep 30 '20 at 13:00