Search code examples
c#androidxamarin.androidbroadcastreceiveralarmmanager

Xamarin Android: Reopen app at specific time


I'm working on trying to create an alarm clock type app in Xamarin, what I am working on right now is trying to get the app to open back up at a specific time no matter what(excluding restart I know thats onbootcompleted) but when I close the app right now the AlarmManager broadcast is never fired off and the app never reopens.

Right now im trying to test by setting the alarmmanager to pop the broadcast 1 minute after I click the button.

Broadcast Receiver

using Android.Content;

namespace (removed)
{
    [BroadcastReceiver]
    public class ServiceEventHandler : BroadcastReceiver
    {
        public override void OnReceive(Context context, Intent intent)
        {
            Intent temp = new Intent();
            temp.SetClass(context, typeof(MainActivity));
            temp.SetFlags(ActivityFlags.NewTask);
            context.StartActivity(temp);
        }
    }
}

Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="(removed)" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="24" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.PERSISTENT_ACTIVITY" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <application android:icon="@drawable/Icon" android:label="(removed)">
        <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
        <meta-data android:name="com.google.android.geo.API_KEY" android:value="(removed)" />
    </application>
</manifest>

Method that starts the alarm

private void Test_Click(object sender, EventArgs e)
        {
            //StartService(new Intent(this, typeof(TestService)));
            Intent wake = new Intent(this, typeof(ServiceEventHandler));
            PendingIntent pending = PendingIntent.GetBroadcast(this, 0, wake, 0);

            var alarmMgr = (AlarmManager)GetSystemService(AlarmService);

            Calendar cal = Calendar.GetInstance(Java.Util.TimeZone.Default);
            cal.Set(CalendarField.HourOfDay, DateTime.Now.Hour);
            cal.Set(CalendarField.Minute, DateTime.Now.Minute + 1);
            cal.Set(CalendarField.Minute, cal.Get(CalendarField.Minute) + 1);//One minute for test

            alarmMgr.Set(AlarmType.RtcWakeup, cal.TimeInMillis, pending);
        }

Solution

  • Ultimately after a few weeks of playing with everything I finally discovered a dependable way to make an alarm go off no matter what. Overall there is a LOT of documentation on this process, but I've found that a lot of it is not all in one place.

    So here is what I did:

    An alarm set in the AlarmManager is only persistent if the application that set it is always running.

    So the solution is to have a background service that runs and manages all of your alarms.

    The Code:

    I found that using Cal left a lot of room for error especially when using xamarin, so instead I used the current system time, and added the span between now and when I want the alarm to go off

    TimeSpan span = time - DateTime.Now;
    long schedule = (long)(Java.Lang.JavaSystem.CurrentTimeMillis() + span.TotalMilliseconds);
    

    This is the full alarm code:

        private void SetAlarm(DateTime time, string alarmID, int eventID, bool final)
        {
            if (!final)
            {
                Intent wake = new Intent(this, typeof(Check//Removed//Event));
                wake.PutExtra("alarmID", alarmID);
                PendingIntent temp = PendingIntent.GetBroadcast(this, eventID, wake, PendingIntentFlags.NoCreate);
                if (temp != null)
                {
                    temp.Cancel();
                }
                PendingIntent pending = PendingIntent.GetBroadcast(this, eventID, wake, PendingIntentFlags.UpdateCurrent);
    
                var alarmMgr = (AlarmManager)GetSystemService(AlarmService);
    
                TimeSpan span = time - DateTime.Now;
    
                long schedule = (long)(Java.Lang.JavaSystem.CurrentTimeMillis() + span.TotalMilliseconds);
    
                alarmMgr.SetExactAndAllowWhileIdle(AlarmType.RtcWakeup, schedule, pending);
            }
            else
            {//Set Alarm
                Intent wake = new Intent(this, typeof(//Removed//Event));
                wake.PutExtra("alarmID", alarmID);
                PendingIntent temp = PendingIntent.GetBroadcast(this, eventID, wake, PendingIntentFlags.NoCreate);
                if (temp != null)
                {
                    temp.Cancel();
                }
                PendingIntent pending = PendingIntent.GetBroadcast(this, eventID, wake, PendingIntentFlags.UpdateCurrent);
    
                var alarmMgr = (AlarmManager)GetSystemService(AlarmService);
    
                TimeSpan span = time - DateTime.Now;
                long schedule = (long)(Java.Lang.JavaSystem.CurrentTimeMillis() + span.TotalMilliseconds);
    
                alarmMgr.SetExactAndAllowWhileIdle(AlarmType.RtcWakeup, schedule, pending);
    
            }
        }
    }
    

    That sets the alarm accurately. As for making everything persistent, I made the service sticky, and set it to reset all alarms each time its reset. This is accomplished by checking if the broadcast already exists, then deleting it and rescheduling if it does.

    PendingIntent temp = PendingIntent.GetBroadcast(this, eventID, wake, PendingIntentFlags.NoCreate);
    if (temp != null)
    {
        temp.Cancel();
    }
    

    The combination of this and a broadcast receiver that gets the startup notification makes the alarms as persistent as possible.