I have problems with Oreo 8.1 and Pie 9.0. When my pedometer app is open or on the background it is counting steps, but not when the app is closed. Also after closing app notification icon disappearing from the phone screen. Tested on Oreo 8.0 using foreground service and it is working fine with notification icon on top left corner of phone screen and counting steps when the app is closed. Tested also on android Nougat without foreground services and working fine. I cannot find the problem maybe it is something with remove, destroy, resume, stop, pause functions?
BootReceiver.cs
[BroadcastReceiver(Enabled = true, Exported = true, DirectBootAware = true)]
[IntentFilter(new string[] { Intent.ActionBootCompleted, Intent.ActionLockedBootCompleted, "android.intent.action.QUICKBOOT_POWERON", "com.htc.intent.action.QUICKBOOT_POWERON" })]
public class BootReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
var stepServiceIntent = new Intent(context, typeof(StepService));
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
context.StartForegroundService(stepServiceIntent);
}
else
{
context.StartService(stepServiceIntent);
}
}
}
StepServiceBinder.cs
public class StepServiceBinder : Binder
{
StepService stepService;
public StepServiceBinder(StepService service)
{
this.stepService = service;
}
public StepService StepService
{
get { return stepService; }
}
}
StepServiceConnection.cs
public class StepServiceConnection : Java.Lang.Object, IServiceConnection
{
MainActivity activity;
public StepServiceConnection(MainActivity activity)
{
this.activity = activity;
}
public void OnServiceConnected(ComponentName name, IBinder service)
{
var serviceBinder = service as StepServiceBinder;
if (serviceBinder != null)
{
activity.Binder = serviceBinder;
activity.IsBound = true;
}
}
public void OnServiceDisconnected(ComponentName name)
{
activity.IsBound = false;
}
}
MainActivity.cs
[Activity(Label = "My Pedometer", Icon = "@mipmap/icon", Theme = "@style/MainTheme", LaunchMode = LaunchMode.SingleTask, MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
public bool IsBound { get; set; }
private StepServiceBinder binder;
private bool registered;
private Handler handler;
private bool firstRun = true;
private StepServiceConnection serviceConnection;
public StepServiceBinder Binder
{
get { return binder; }
set
{
binder = value;
if (binder == null)
return;
HandlePropertyChanged(null, new System.ComponentModel.PropertyChangedEventArgs("StepsToday"));
if (registered)
binder.StepService.PropertyChanged -= HandlePropertyChanged;
binder.StepService.PropertyChanged += HandlePropertyChanged;
registered = true;
}
}
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Xamarin.Forms.Forms.Init(this, savedInstanceState);
StartStepService();
handler = new Handler();
handler.PostDelayed(() => UpdateUI(), 500);
LoadApplication(new App());
}
private void StartStepService()
{
try
{
var service = new Intent(this, typeof(StepService));
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
StartForegroundService(service);
}
else
{
StartService(service);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception {0}.", ex.Message);
}
}
protected override void OnStop()
{
base.OnStop();
if (IsBound)
{
UnbindService(serviceConnection);
IsBound = false;
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if (IsBound)
{
UnbindService(serviceConnection);
IsBound = false;
}
}
protected override void OnStart()
{
base.OnStart();
if (!firstRun)
StartStepService();
if (IsBound)
return;
var serviceIntent = new Intent(this, typeof(StepService));
serviceConnection = new StepServiceConnection(this);
BindService(serviceIntent, serviceConnection, Bind.AutoCreate);
}
protected override void OnPause()
{
base.OnPause();
if (registered && binder != null)
{
binder.StepService.PropertyChanged -= HandlePropertyChanged;
registered = false;
}
}
protected override void OnResume()
{
base.OnResume();
if (!firstRun)
{
if (handler == null)
handler = new Handler();
handler.PostDelayed(() => UpdateUI(), 500);
}
firstRun = false;
if (!registered && binder != null)
{
binder.StepService.PropertyChanged += HandlePropertyChanged;
registered = true;
}
}
void HandlePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName != "StepsToday")
return;
UpdateUI();
}
private void UpdateUI()
{
RunOnUiThread(() =>
{
long steps = 0;
var showWaring = false;
if (Binder == null)
{
if (Utils.IsSameDay)
steps = Settings.CurrentDaySteps;
}
else
{
steps = Binder.StepService.StepsToday;
showWaring = binder.StepService.WarningState;
}
Settings.CurrentDaySteps = steps;
});
}
}
StepService.cs
[Service(Enabled = true)]
[IntentFilter(new String[] { "com.PedometerApp.StepService" })]
public class StepService : Service, ISensorEventListener, INotifyPropertyChanged
{
private SensorManager sManager;
private bool isRunning;
private long stepsToday = 0;
public bool WarningState
{
get;
set;
}
public long StepsToday
{
get { return stepsToday; }
set
{
if (stepsToday == value)
return;
stepsToday = value;
OnPropertyChanged("StepsToday");
Settings.CurrentDaySteps = value;
MessagingCenter.Send<object, long>(this, "Steps", Settings.CurrentDaySteps);
}
}
public const string PRIMARY_NOTIF_CHANNEL = "exampleChannel";
public const int SERVICE_RUNNING_NOTIFICATION_ID = 10000;
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
RegisterForService();
var warning = false;
if (intent != null)
warning = intent.GetBooleanExtra("warning", false);
Startup();
return StartCommandResult.Sticky;
}
private void RegisterForService()
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
var channel = new NotificationChannel(PRIMARY_NOTIF_CHANNEL, "Pedometer Service Channel", NotificationImportance.Low)
{
Description = "Foreground Service Channel"
};
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
var notification = new Notification.Builder(this, PRIMARY_NOTIF_CHANNEL)
.SetContentTitle("Service")
.SetContentText("Running")
.SetSmallIcon(Resource.Drawable.ic_stat_name)
.SetContentIntent(BuildIntentToShowMainActivity())
.SetOngoing(true)
.Build();
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
}
else
{
BuildIntentToShowMainActivity();
}
}
PendingIntent BuildIntentToShowMainActivity()
{
var alarmManager = ((AlarmManager)ApplicationContext.GetSystemService(AlarmService));
var intent2 = new Intent(this, typeof(StepService));
intent2.PutExtra("warning", WarningState);
var stepIntent = PendingIntent.GetService(ApplicationContext, 200, intent2, PendingIntentFlags.UpdateCurrent);
alarmManager.Set(AlarmType.Rtc, Java.Lang.JavaSystem
.CurrentTimeMillis() + 1000 * 60 * 60, stepIntent);
return stepIntent;
}
public override void OnTaskRemoved(Intent rootIntent)
{
base.OnTaskRemoved(rootIntent);
UnregisterListeners();
var intent = new Intent(this, typeof(StepService));
intent.PutExtra("warning", WarningState);
((AlarmManager)GetSystemService(AlarmService)).Set(AlarmType.Rtc, Java.Lang.JavaSystem
.CurrentTimeMillis() + 500,
PendingIntent.GetService(this, 201, intent, 0));
}
private void Startup(bool warning = false)
{
CrunchDates(true);
if (!isRunning)
{
RegisterListeners();
WarningState = warning;
}
isRunning = true;
}
public override void OnDestroy()
{
base.OnDestroy();
UnregisterListeners();
isRunning = false;
CrunchDates();
}
void RegisterListeners()
{
sManager = GetSystemService(SensorService) as SensorManager;
sManager.RegisterListener(this, sManager.GetDefaultSensor(SensorType.StepCounter), SensorDelay.Ui);
}
void UnregisterListeners()
{
if (!isRunning)
return;
try
{
var sensorManager = (SensorManager)GetSystemService(Context.SensorService);
sensorManager.UnregisterListener(this);
isRunning = false;
}
catch (Exception ex)
{
}
}
StepServiceBinder binder;
public override Android.OS.IBinder OnBind(Android.Content.Intent intent)
{
binder = new StepServiceBinder(this);
return binder;
}
public void OnAccuracyChanged(Sensor sensor, SensorStatus accuracy)
{
//do nothing here
}
public void AddSteps(long count)
{
if (lastSteps == 0)
{
lastSteps = count;
}
newSteps = count - lastSteps;
if (newSteps < 0)
newSteps = 1;
else if (newSteps > 100)
newSteps = 1;
lastSteps = count;
CrunchDates();
Settings.TotalSteps += newSteps;
StepsToday = Settings.TotalSteps - Settings.StepsBeforeToday;
}
long newSteps = 0;
long lastSteps = 0;
public void OnSensorChanged(SensorEvent e)
{
if (lastSteps < 0)
lastSteps = 0;
var count = (long)e.Values[0];
WarningState = false;
AddSteps(count);
}
private void CrunchDates(bool startup = false)
{
if (!Utils.IsSameDay)
{
var yesterday = Settings.CurrentDay;
var dayEntry = StepEntryManager.GetStepEntry(yesterday);
if (dayEntry == null || dayEntry.Date.DayOfYear != yesterday.DayOfYear)
{
dayEntry = new StepEntry();
}
dayEntry.Date = yesterday;
dayEntry.Steps = Settings.CurrentDaySteps;
Settings.CurrentDay = DateTime.Today;
Settings.CurrentDaySteps = 0;
Settings.StepsBeforeToday = Settings.TotalSteps;
StepsToday = 0;
try
{
StepEntryManager.SaveStepEntry(dayEntry);
}
catch (Exception ex)
{
Console.WriteLine("Error {0}", ex.Message);
}
}
else if (startup)
{
StepsToday = Settings.TotalSteps - Settings.StepsBeforeToday;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
***Manifest.xml ***
<manifest android:versionName="1.1" package="com.PedometerApp" android:installLocation="internalOnly" android:versionCode="11">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="27" />
<application android:label="My Pedometer" android:icon="@drawable/logo"> </application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
</manifest>