protected override async Task ActuallyResetReminders(AccountDataItem account, AccountDataStore data)
        {
            try
            {
                // If reminders disabled, do nothing
                if (!account.RemindersDayBefore && !account.RemindersDayOf)
                {
                    return;
                }

                await Task.Run(async delegate
                {
                    // This gets called whenever changes are made in the account's data.
                    AgendaViewItemsGroup agendaItems = await GetAgendaViewItemsGroup(account, DateTime.Now);

                    // If no current semester, or no items, just clear all
                    if (agendaItems == null || agendaItems.Items.Count == 0)
                    {
                        await RemoveAllNotificationsAsync(account.LocalAccountId);
                        return;
                    }

                    DateTime now = DateTime.Now;

                    await UpdateScheduledNotificationsAsync(account, agendaItems, now);
                });
            }
            catch (Exception ex)
            {
                TelemetryExtension.Current?.TrackException(ex);
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Guaranteed that data won't be null
        /// </summary>
        /// <param name="data"></param>
        /// <returns>Should always return an initialized list.</returns>
        private static async Task <List <BaseViewItemHomeworkExam> > getAllUpcomingBlocking(AccountDataItem account, AccountDataStore data, DateTime todayAsUtc, BaseUpcomingTileSettings tileSettings)
        {
            var currSemesterId = account.CurrentSemesterId;

            if (currSemesterId == Guid.Empty)
            {
                return(new List <BaseViewItemHomeworkExam>());
            }

            ScheduleViewItemsGroup scheduleViewGroup;

            try
            {
                scheduleViewGroup = await ScheduleViewItemsGroup.LoadAsync(account.LocalAccountId, account.CurrentSemesterId, trackChanges : true, includeWeightCategories : false);
            }
            catch
            {
                // If semester not found
                return(new List <BaseViewItemHomeworkExam>());
            }

            DateTime dateToStartDisplayingFrom = DateTime.SpecifyKind(tileSettings.GetDateToStartDisplayingOn(todayAsUtc), DateTimeKind.Utc);

            var agendaViewGroup = await AgendaViewItemsGroup.LoadAsync(account.LocalAccountId, scheduleViewGroup.Semester, DateTime.SpecifyKind(todayAsUtc, DateTimeKind.Local), trackChanges : true);

            // We're not going to worry about locking changes while we enumerate, since if collection changes while we're enumerating, there'll be a
            // new incoming reset request anyways

            // Agenda view group doesn't sort, so we have to sort it
            return(agendaViewGroup.Items.Where(
                       i => i.Date.Date >= dateToStartDisplayingFrom &&
                       ((tileSettings.ShowHomework && i is ViewItemHomework) || (tileSettings.ShowExams && i is ViewItemExam))
                       ).OrderBy(i => i).ToList());
        }
Esempio n. 3
0
        protected override async Task LoadAsyncOverride()
        {
            AgendaViewItemsGroup = await AgendaViewItemsGroup.LoadAsync(MainScreenViewModel.CurrentLocalAccountId, MainScreenViewModel.CurrentSemester, Today);

            AgendaViewItemsGroup.Items.CollectionChanged += new WeakEventHandler <NotifyCollectionChangedEventArgs>(Items_CollectionChanged).Handler;
            UpdateHasNoItems();

            ListenToLocalEditsFor <DataLayer.DataItems.DataItemMegaItem>().ChangedItems += ItemsLocallyEditedListener_ChangedItems;
        }
        private static async Task <AgendaViewItemsGroup> GetAgendaViewItemsGroup(AccountDataItem account, DateTime today)
        {
            try
            {
                // We're getting cached versions which might change as we're using the app, but that's fine, since if items change, another
                // ResetReminders will be incoming anyways
                var scheduleViewItemsGroup = await ScheduleViewItemsGroup.LoadAsync(account.LocalAccountId, account.CurrentSemesterId, trackChanges : true, includeWeightCategories : false);

                return(await AgendaViewItemsGroup.LoadAsync(account.LocalAccountId, scheduleViewItemsGroup.Semester, today, trackChanges : true));
            }

            // If semester didn't exist, it throws null reference exception
            catch { return(null); }
        }
        private async Task AddScheduledDayOfNotifications(AccountDataItem account, AgendaViewItemsGroup agendaItems, DateTime now)
        {
            try
            {
                DateTime today   = now.Date;
                DateTime maxDate = today.AddDays(DAYS_IN_ADVANCE);

                BaseViewItemHomeworkExam[] itemsDueTodayOrGreater = agendaItems.Items.Where(i => i.Date.Date >= today && i.Date.Date <= maxDate).OrderBy(i => i).ToArray();

                foreach (var item in itemsDueTodayOrGreater)
                {
                    bool hasClassTime = false;

                    DateTime reminderTime = GetDayOfReminderTime(item, out hasClassTime);

                    if (!IsTimeOkay(reminderTime))
                    {
                        continue;
                    }

                    string title = StringTools.TrimLengthWithEllipses(item.Name, 150);

                    string subtitle = GetDueTimeAsString(item);

                    string body = GetClassName(item);
                    if (!string.IsNullOrWhiteSpace(item.Details))
                    {
                        body += " - " + StringTools.TrimLengthWithEllipses(item.Details.Trim(), 200);
                    }

                    await ScheduleDayOfNotification(account.LocalAccountId, item, title, subtitle, body, reminderTime);
                }
            }
            catch (Exception ex)
            {
                TelemetryExtension.Current?.TrackException(ex);
            }
        }
        public static DateTime GetDayBeforeReminderTime(DateTime date, AccountDataItem account, AgendaViewItemsGroup agenda)
        {
            DateTime?classEndTime = account.GetClassEndTime(date, agenda.Classes);

            if (classEndTime == null)
            {
                return(date.Date.AddHours(15));        // Default time is 3:00 PM
            }
            return(classEndTime.Value.AddMinutes(10)); // Otherwise 10 mins after the class end time
        }
        public static void ScheduleDayBeforeAlarm(Context context, AccountDataItem account, DateTime today, AgendaViewItemsGroup agendaItems)
        {
            if (agendaItems == null)
            {
                return;
            }

            DateTime dayBeforeReminderTime = RemindersExtension.GetDayBeforeReminderTime(today, account, agendaItems);

            DateTime timeToScheduleAt;

            // If we haven't reached that time yet for "due tomorrow"
            if (dayBeforeReminderTime > DateTime.Now)
            {
                timeToScheduleAt = dayBeforeReminderTime.AddMilliseconds(1);
            }

            // Otherwise we'll need to set the timer for the "due today"
            else
            {
                timeToScheduleAt = today.AddDays(1).AddMilliseconds(1);
            }

            AlarmManagerHelper.Schedule(
                context: context,
                receiverType: typeof(UpdateDayBeforeNotificationReceiver),
                wakeTime: timeToScheduleAt,
                uriData: "powerplanner:?localAccountId=" + account.LocalAccountId,
                wakeDevice: true);
        }
        private static async Task UpdateDayBeforeNotification(AccountDataItem account, AgendaViewItemsGroup agendaItems, Context context, NotificationManager notificationManager, bool fromForeground)
        {
            string tag = account.LocalAccountId.ToString();

            // If no current semester, or no items, then just clear
            if (agendaItems == null || agendaItems.Items.Count == 0)
            {
                notificationManager.Cancel(tag, NotificationIds.DAY_BEFORE_NOTIFICATION);
                return;
            }

            StatusBarNotification existing;

            if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.M)
            {
                existing = notificationManager.GetActiveNotifications().FirstOrDefault(i => i.Id == NotificationIds.DAY_BEFORE_NOTIFICATION && i.Tag == tag);
            }
            else
            {
                // GetActiveNotifications didn't exist till API 23
                existing = null;
            }

            const string EXTRA_UNIQUE_ID = "UniqueId";

            DateTime now = DateTime.Now;

            DateTime reminderTime = RemindersExtension.GetDayBeforeReminderTime(now.Date, account, agendaItems);

            bool sendingDueTomorrow = now >= reminderTime;

            NotificationCompat.Builder builder;

            if (sendingDueTomorrow)
            {
                var itemsDueTomorrow = agendaItems.Items.Where(i => i.Date.Date == now.Date.AddDays(1)).OrderBy(i => i).ToArray();

                // If nothing tomorrow, we clear
                if (itemsDueTomorrow.Length == 0)
                {
                    notificationManager.Cancel(tag, NotificationIds.DAY_BEFORE_NOTIFICATION);
                    return;
                }

                string extraUniqueId = "DueTomorrow" + string.Join(";", itemsDueTomorrow.Select(i => i.Name)).GetHashCode();

                // If already updated
                if (existing != null && existing.Notification.Extras.GetString(EXTRA_UNIQUE_ID).Equals(extraUniqueId))
                {
                    return;
                }

                // If all the items have been updated more recently than the reminder time, we won't show a new notification
                if (existing == null && itemsDueTomorrow.All(i => i.Updated.ToLocalTime() > reminderTime))
                {
                    return;
                }

                // If we already sent the notification today
                if (account.DateLastDayBeforeReminderWasSent.Date == now.Date)
                {
                    // If the notification has been dismissed, do nothing
                    if (existing == null)
                    {
                        return;
                    }
                }

                // Otherwise we need to update the date that it was sent
                else
                {
                    account.DateLastDayBeforeReminderWasSent = now;
                    await AccountsManager.Save(account);
                }

                if (existing == null && fromForeground)
                {
                    return;
                }

                builder = CreateReminderBuilderForDayBefore(context, account.LocalAccountId);
                Bundle b = new Bundle();
                b.PutString(EXTRA_UNIQUE_ID, extraUniqueId);
                builder.SetExtras(b);
                // "Due tomorrow"
                builder.SetContentTitle(PowerPlannerResources.GetDueX(PowerPlannerResources.GetRelativeDateTomorrow().ToLower()));
                PopulateNotificationDayBeforeWithItems(builder, itemsDueTomorrow);
            }

            // Otherwise "due today"
            else
            {
                var itemsDueToday = agendaItems.Items.Where(i => i.Date.Date == now.Date).OrderBy(i => i).ToArray();

                // If nothing left today, we clear
                if (itemsDueToday.Length == 0)
                {
                    notificationManager.Cancel(tag, NotificationIds.DAY_BEFORE_NOTIFICATION);
                    return;
                }

                string extraUniqueId = "DueToday" + string.Join(";", itemsDueToday.Select(i => i.Name)).GetHashCode();

                // If already updated
                if (existing != null && existing.Notification.Extras.GetString(EXTRA_UNIQUE_ID).Equals(extraUniqueId))
                {
                    return;
                }

                // If the notification doesn't currently exist (user potentially dismissed it) and we've already sent either yesterday or today
                if (existing == null && account.DateLastDayBeforeReminderWasSent.Date == DateTime.Today.AddDays(-1) || account.DateLastDayBeforeReminderWasSent.Date == DateTime.Today)
                {
                    return;
                }

                // If all the items have been updated more recently than the reminder time, we won't show a new notification
                if (existing == null && itemsDueToday.All(i => i.Updated.ToLocalTime() > reminderTime))
                {
                    return;
                }

                // If we already sent the notification yesterday
                if (account.DateLastDayBeforeReminderWasSent.Date == now.Date.AddDays(-1))
                {
                    // If the notification has been dismissed, do nothing
                    if (existing == null)
                    {
                        return;
                    }
                }

                // Otherwise we need to update the date that it was sent
                else
                {
                    account.DateLastDayBeforeReminderWasSent = now.Date.AddDays(-1);
                    await AccountsManager.Save(account);
                }

                if (existing == null && fromForeground)
                {
                    return;
                }

                builder = CreateReminderBuilderForDayBefore(context, account.LocalAccountId);
                Bundle b = new Bundle();
                b.PutString(EXTRA_UNIQUE_ID, extraUniqueId);
                builder.SetExtras(b);
                // "Due today"
                builder.SetContentTitle(PowerPlannerResources.GetDueX(PowerPlannerResources.GetRelativeDateToday().ToLower()));
                PopulateNotificationDayBeforeWithItems(builder, itemsDueToday);
            }

            notificationManager.Notify(tag, NotificationIds.DAY_BEFORE_NOTIFICATION, BuildReminder(builder));
        }
        /// <summary>
        /// Returns a list of items that should have active notifications (ignoring whether user already dismissed them)
        /// </summary>
        /// <param name="account"></param>
        /// <param name="agendaItems"></param>
        /// <param name="now"></param>
        /// <param name="nextReminderTime"></param>
        /// <returns></returns>
        private static List <Tuple <BaseViewItemHomeworkExam, DateTime> > GetItemsThatCouldHaveDayOfNotifications(AccountDataItem account, AgendaViewItemsGroup agendaItems, DateTime now, out DateTime nextReminderTime)
        {
            List <Tuple <BaseViewItemHomeworkExam, DateTime> > shouldBeActive = new List <Tuple <BaseViewItemHomeworkExam, DateTime> >();

            nextReminderTime = DateTime.MinValue;
            PowerPlannerSending.Schedule.Week currentWeek = account.GetWeekOnDifferentDate(now);

            // Add past-due incomplete tasks (we don't add exams since they're "complete" when they're past-due)
            foreach (var task in agendaItems.Items.OfType <ViewItemHomework>().Where(i => i.Date.Date < now.Date).OrderBy(i => i))
            {
                shouldBeActive.Add(new Tuple <BaseViewItemHomeworkExam, DateTime>(task, task.Date.Date));
            }

            // Look at items due today
            // NOTE: Assuming that end time is always on the same day. If we allow events that span multiple days,
            // we might need to update this so it correctly expires the event on the future date.
            // To make sure we include items due 00:30 on the next day, we have to factor in tomorrow
            DateTime tomorrow        = now.Date.AddDays(1);
            var      weekForTomorrow = account.GetWeekOnDifferentDate(tomorrow);

            foreach (var item in agendaItems.Items.Where(i => i.Date.Date == now.Date || i.Date.Date == tomorrow).OrderBy(i => i))
            {
                // Get the reminder time
                DateTime reminderTime = GetDayOfReminderTime(item.Date.Date == tomorrow ? weekForTomorrow : currentWeek, item);

                // If it's past the reminder time
                if (now >= reminderTime)
                {
                    // If it has an end time
                    DateTime endTime;
                    if (item.TryGetEndDateWithTime(out endTime))
                    {
                        // We have to consider the end time for updating reminders, since events
                        // expire once they're over (so need to update again right when an event completes)
                        if (endTime > now && (nextReminderTime == DateTime.MinValue || endTime < nextReminderTime))
                        {
                            shouldBeActive.Add(new Tuple <BaseViewItemHomeworkExam, DateTime>(item, reminderTime));
                            nextReminderTime = endTime;
                        }
                    }

                    else
                    {
                        // Otherwise add it
                        shouldBeActive.Add(new Tuple <BaseViewItemHomeworkExam, DateTime>(item, reminderTime));
                    }
                }
                else if (nextReminderTime == DateTime.MinValue || reminderTime < nextReminderTime)
                {
                    nextReminderTime = reminderTime;
                }
            }

            // If no time found, we pick an item that's due in the future
            if (nextReminderTime == DateTime.MinValue)
            {
                DateTime?nextDateWithItems = agendaItems.Items.Where(i => i.Date.Date > now.Date).OrderBy(i => i.Date.Date).FirstOrDefault()?.Date.Date;
                if (nextDateWithItems != null)
                {
                    // Get the week for that date
                    var week = account.GetWeekOnDifferentDate(nextDateWithItems.Value);

                    // Look through all items on that date
                    foreach (var item in agendaItems.Items.Where(i => i.Date.Date == nextDateWithItems.Value))
                    {
                        // Pick the smallest reminder time
                        DateTime reminderTime = GetDayOfReminderTime(currentWeek, item);
                        if (nextReminderTime == DateTime.MinValue || reminderTime < nextReminderTime)
                        {
                            nextReminderTime = reminderTime;
                        }
                    }
                }
            }

            return(shouldBeActive);
        }
        private static async Task UpdateAndScheduleDayOfNotifications(AccountDataItem account, AgendaViewItemsGroup agendaItems, Context context, NotificationManager notificationManager, DateTime now, bool fromForeground)
        {
            string tagForAccountStartsWith = account.LocalAccountId.ToString();
            List <StatusBarNotification> existingNotifs;

            if (Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.M)
            {
                existingNotifs = notificationManager.GetActiveNotifications().Where(i => i.Id == NotificationIds.DAY_OF_NOTIFICATIONS && i.Tag != null && i.Tag.StartsWith(tagForAccountStartsWith)).ToList();
            }
            else
            {
                // GetActiveNotifications didn't exist before version 23
                existingNotifs = new List <StatusBarNotification>();
            }

            // If no current semester, or no items, then just clear
            if (agendaItems == null || agendaItems.Items.Count == 0)
            {
                foreach (var notif in existingNotifs)
                {
                    notificationManager.Cancel(notif.Tag, NotificationIds.DAY_OF_NOTIFICATIONS);
                }
                return;
            }

            DateTime nextReminderTime;

            var itemsThatCouldHaveNotifs = GetItemsThatCouldHaveDayOfNotifications(account, agendaItems, now, out nextReminderTime);

            // Remove ones that don't exist anymore
            for (int i = 0; i < existingNotifs.Count; i++)
            {
                var  existing = existingNotifs[i];
                Guid identifier;
                if (TryGetItemIdFromDayOfTag(existing.Tag, out identifier))
                {
                    if (!itemsThatCouldHaveNotifs.Any(x => x.Item1.Identifier == identifier))
                    {
                        notificationManager.Cancel(existing.Tag, NotificationIds.DAY_OF_NOTIFICATIONS);
                        existingNotifs.RemoveAt(i);
                        i--;
                    }
                }
            }

            foreach (var item in itemsThatCouldHaveNotifs)
            {
                UpdateDayOfNotification(account.LocalAccountId, item.Item1, item.Item2, context, notificationManager, existingNotifs, fromForeground);
            }

            // Need to mark them as sent
            Guid[] newlySentHomeworkReminders = itemsThatCouldHaveNotifs.Select(i => i.Item1).OfType <ViewItemHomework>().Where(i => !i.HasSentReminder).Select(i => i.Identifier).ToArray();
            Guid[] newlySentExamReminders     = itemsThatCouldHaveNotifs.Select(i => i.Item1).OfType <ViewItemExam>().Where(i => !i.HasSentReminder).Select(i => i.Identifier).ToArray();

            if (newlySentHomeworkReminders.Length > 0 || newlySentExamReminders.Length > 0)
            {
                var dataStore = await AccountDataStore.Get(account.LocalAccountId);

                if (dataStore != null)
                {
                    await dataStore.MarkAndroidRemindersSent(newlySentHomeworkReminders, newlySentExamReminders);
                }
            }

            // Schedule the next alarm
            if (nextReminderTime > now)
            {
                AlarmManagerHelper.Schedule(
                    context: context,
                    receiverType: typeof(UpdateDayOfNotificationsReceiver),
                    wakeTime: nextReminderTime,
                    uriData: "powerplanner:?localAccountId=" + account.LocalAccountId,
                    wakeDevice: true);
            }
        }
        private static async Task UpdateAndScheduleDayBeforeNotification(Context context, AccountDataItem account, AgendaViewItemsGroup agendaItems, NotificationManager notificationManager, DateTime now, bool fromForeground)
        {
            // Update the day before notification
            await UpdateDayBeforeNotification(account, agendaItems, context, notificationManager, fromForeground);

            // And then schedule the next alarm
            ScheduleDayBeforeAlarm(context, account, now.Date, agendaItems);
        }
        private async Task AddScheduledDayBeforeNotifications(AccountDataItem account, AgendaViewItemsGroup agendaItems, DateTime now)
        {
            try
            {
                string idStartString = GetIdentifierStartStringForDayBefore(account.LocalAccountId);

                DateTime artificialToday = now.Date;
                for (int i = 0; i < DAYS_IN_ADVANCE; i++, artificialToday = artificialToday.AddDays(1))
                {
                    DateTime artificialTomorrow           = artificialToday.AddDays(1);
                    BaseViewItemHomeworkExam[] itemsOnDay = agendaItems.Items.Where(x => x.Date.Date == artificialTomorrow).OrderBy(x => x).ToArray();

                    if (itemsOnDay.Length > 0)
                    {
                        DateTime reminderTime = RemindersExtension.GetDayBeforeReminderTime(artificialToday, account, agendaItems);

                        string title = "Due tomorrow";
                        string body  = "";
                        if (itemsOnDay.Length == 1)
                        {
                            body = StringTools.TrimLengthWithEllipses(itemsOnDay[0].Name, 200);
                        }
                        else
                        {
                            title += " (" + itemsOnDay.Length + ")";

                            foreach (var item in itemsOnDay.Take(5))
                            {
                                body += StringTools.TrimLengthWithEllipses(item.Name, 30) + "\n";
                            }
                            body = body.Trim();
                        }
                        await ScheduleDayBeforeNotification(idStartString + artificialToday.ToString(DAY_BEFORE_DATE_FORMAT, CultureInfo.InvariantCulture), title, body, reminderTime);
                    }
                }
            }
            catch (Exception ex)
            {
                TelemetryExtension.Current?.TrackException(ex);
            }
        }
        private async Task UpdateScheduledNotificationsAsync(AccountDataItem account, AgendaViewItemsGroup agendaItems, DateTime now)
        {
            // Just remove all, we would be overwriting them anyways
            await RemoveAllScheduledNotificationsAsync(account.LocalAccountId);

            await AddScheduledDayBeforeNotifications(account, agendaItems, now);
            await AddScheduledDayOfNotifications(account, agendaItems, now);
        }
        private async Task ResetReminders(AccountDataItem account, CancellationToken token)
        {
            if (account == null)
            {
                return;
            }

            ClearReminders(account.LocalAccountId, token);

            token.ThrowIfCancellationRequested();

            var semesterId = account.CurrentSemesterId;

            if (semesterId == Guid.Empty)
            {
                return;
            }

            DateTime todayAsUtc = DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc);

            ViewItemTaskOrEvent[] itemsDueTodayOrGreater;
            ViewItemSchedule[]    allSchedules;

            try
            {
                ScheduleViewItemsGroup viewModelSchedule = await ScheduleViewItemsGroup.LoadAsync(account.LocalAccountId, semesterId, trackChanges : true, includeWeightCategories : false);

                AgendaViewItemsGroup viewModel = await AgendaViewItemsGroup.LoadAsync(account.LocalAccountId, viewModelSchedule.Semester, DateTime.SpecifyKind(todayAsUtc, DateTimeKind.Local), trackChanges : true);

                // Data we need to load and hold are...
                // - Items due today or greater
                // - All schedules
                // We don't need to worry about holding a lock though, if the collections change and that breaks us, that's fine,
                // that implies that the data has been changed anyways and another ResetReminders will come in.
                // Therefore we should also expect to get some exceptions here.

                allSchedules           = viewModelSchedule.Classes.SelectMany(i => i.Schedules).ToArray();
                itemsDueTodayOrGreater = viewModel.Items.Where(i => i.Date.Date >= DateTime.SpecifyKind(todayAsUtc, DateTimeKind.Local)).ToArray();
            }
            catch
            {
                // Semester wasn't found or other misc error
                return;
            }

            // Since we're no longer inside the lock and we're using a view group that tracks changes, any properties we access on the view items could change at any time.
            // Therefore we need to take that into consideration and be careful about what we do.

            if (account.RemindersDayBefore)
            {
                Dictionary <DateTime, List <ViewItemTaskOrEvent> > groupedByDay = new Dictionary <DateTime, List <ViewItemTaskOrEvent> >();

                DateTime tomorrow = DateTime.SpecifyKind(todayAsUtc.AddDays(1), DateTimeKind.Local);

                //select all incomplete tasks/events that is due on tomorrow or later
                foreach (ViewItemTaskOrEvent h in itemsDueTodayOrGreater.Where(i => i.Date.Date >= tomorrow))
                {
                    token.ThrowIfCancellationRequested();

                    var hDate = h.Date.Date;

                    if (!groupedByDay.TryGetValue(hDate, out List <ViewItemTaskOrEvent> group))
                    {
                        group = new List <ViewItemTaskOrEvent>();
                        groupedByDay[hDate] = group;
                    }

                    group.Add(h);
                }

                foreach (var pair in groupedByDay)
                {
                    token.ThrowIfCancellationRequested();

                    DateTime dueOn = pair.Key;
                    List <ViewItemTaskOrEvent> items = pair.Value;

                    DateTime reminderTime = GetDayBeforeReminderTime(dueOn.AddDays(-1), account, allSchedules);

                    if (!IsTimeOkay(reminderTime))
                    {
                        continue;
                    }


                    ViewItemTaskOrEvent[] tasks  = items.Where(i => i.Type == TaskOrEventType.Task).ToArray();
                    ViewItemTaskOrEvent[] events = items.Where(i => i.Type == TaskOrEventType.Event).ToArray();


                    if (tasks.Length > 0)
                    {
                        XmlDocument xml = GenerateToastReminder(
                            tasks.Length == 1 ? "You have 1 item due tomorrow" : "You have " + tasks.Length + " items due tomorrow",
                            GetItemLineText(tasks[0]),
                            tasks.Length >= 2 ? GetItemLineText(tasks[1]) : null,
#pragma warning disable 0612
                            new QueryStringHelper()
#pragma warning restore 0612
                            .SetLocalAccountId(account.LocalAccountId)
                            .SetAction("DayBeforeHomeworkReminder")
                            .ToString()
                            );

                        string remoteId = null;
                        if (account.IsOnlineAccount)
                        {
                            int hashedItems = string.Join(";", tasks.Select(i => i.Identifier)).GetHashCode();
                            remoteId = $"PP_DayBeforeHomeworks_{account.AccountId}_{hashedItems}";
                        }

                        Schedule(
                            GenerateScheduledToastNotification(
                                xml,
                                reminderTime,
                                GetId(account), //id's don't need to be unique
                                remoteId)
                            );
                    }

                    if (events.Length > 0)
                    {
                        XmlDocument xml = GenerateToastReminder(
                            events.Length == 1 ? "You have 1 event tomorrow" : "You have " + events.Length + " events tomorrow",
                            GetItemLineText(events[0]),
                            events.Length >= 2 ? GetItemLineText(events[1]) : null,
#pragma warning disable 0612
                            new QueryStringHelper()
#pragma warning restore 0612
                            .SetLocalAccountId(account.LocalAccountId)
                            .SetAction("DayBeforeExamReminder")
                            .ToString()
                            );

                        string remoteId = null;
                        if (account.IsOnlineAccount)
                        {
                            int hashedItems = string.Join(";", events.Select(i => i.Identifier)).GetHashCode();
                            remoteId = $"PP_DayBeforeExams_{account.AccountId}_{hashedItems}";
                        }

                        Schedule(
                            GenerateScheduledToastNotification(
                                xml,
                                reminderTime,
                                GetId(account),
                                remoteId));
                    }
                }
            }

            if (account.RemindersDayOf)
            {
                foreach (ViewItemTaskOrEvent h in itemsDueTodayOrGreater)
                {
                    token.ThrowIfCancellationRequested();
                    bool hasClassTime = false;

                    DateTime reminderTime = GetDayOfReminderTime(h, ref hasClassTime);

                    if (!IsTimeOkay(reminderTime))
                    {
                        continue;
                    }

                    string subtitle = GetClassName(h) + " - ";

                    if (h.Type == TaskOrEventType.Task)
                    {
                        subtitle += "due ";
                    }

                    if (hasClassTime)
                    {
                        subtitle += "in one hour";
                    }

                    else
                    {
                        subtitle += "today";
                    }

                    XmlDocument xml = GenerateToastReminder(
                        TrimString(h.Name, 200),
                        subtitle,
                        TrimString(h.Details, 200),
                        ArgumentsHelper.CreateArgumentsForView(h, account.LocalAccountId).SerializeToString()
                        );

                    string remoteId = null;
                    if (account.IsOnlineAccount)
                    {
                        remoteId = $"PP_DayOf_{account.AccountId}_{h.Identifier}";
                    }

                    Schedule(
                        GenerateScheduledToastNotification(
                            xml,
                            reminderTime,
                            GetId(account),
                            remoteId));
                }
            }
        }
            private async System.Threading.Tasks.Task InitializeDataAsync()
            {
                List <object> items = null;
                List <BaseViewItemHomeworkExam> tasks = null;

                _now = DateTime.Now;
                bool hasAccount           = false;
                bool isDisabledInSettings = false;
                bool hasSemester          = false;

                try
                {
                    await System.Threading.Tasks.Task.Run(async delegate
                    {
                        var account = await AccountsManager.GetLastLogin();

                        AccountDataStore data = null;

                        if (account != null)
                        {
                            data = await AccountDataStore.Get(account.LocalAccountId);
                        }

                        if (data != null)
                        {
                            hasAccount = true;

                            isDisabledInSettings = account.MainTileSettings.IsDisabled();

                            if (!isDisabledInSettings)
                            {
                                _localAccountId    = account.LocalAccountId;
                                var currSemesterId = account.CurrentSemesterId;
                                if (currSemesterId != Guid.Empty)
                                {
                                    ScheduleViewItemsGroup scheduleViewGroup;
                                    try
                                    {
                                        scheduleViewGroup = await ScheduleViewItemsGroup.LoadAsync(account.LocalAccountId, account.CurrentSemesterId, trackChanges: true, includeWeightCategories: false);
                                    }
                                    catch
                                    {
                                        // If semester not found
                                        scheduleViewGroup = null;
                                    }

                                    if (scheduleViewGroup != null)
                                    {
                                        DateTime dateToStartDisplayingFrom = DateTime.SpecifyKind(account.MainTileSettings.GetDateToStartDisplayingOn(_now.Date), DateTimeKind.Utc);

                                        // We don't track changes since we need a fresh version that has been filtered right now.
                                        // Otherwise when we update for an event expiring, if we have a cached version, that event wouldn't have expired!
                                        var agendaViewGroup = await AgendaViewItemsGroup.LoadAsync(account.LocalAccountId, scheduleViewGroup.Semester, _now.Date, trackChanges: false);
                                        hasSemester         = true;

                                        // We lock the outside, since we are allowing trackChanges on the view items groups (so we have a chance of loading a cached one)... and since we're on a background thread, the lists inside the
                                        // view items groups could change while we're enumerating, hence throwing an exception. So we lock it to ensure this won't happen, and then we return a copy of the items that we need.
                                        using (await agendaViewGroup.DataChangeLock.LockForReadAsync())
                                        {
                                            var filtered = agendaViewGroup.Items
                                                           .Where(i => i.Date.Date >= dateToStartDisplayingFrom);

                                            if (!account.MainTileSettings.ShowHomework)
                                            {
                                                filtered = filtered.Where(i => !(i is ViewItemHomework));
                                            }

                                            if (!account.MainTileSettings.ShowExams)
                                            {
                                                filtered = filtered.Where(i => !(i is ViewItemExam));
                                            }

                                            // Agenda view group doesn't sort, so we have to sort it
                                            tasks = filtered
                                                    .OrderBy(i => i)
                                                    .Take(20)
                                                    .ToList();

                                            // Add date headers
                                            items = new List <object>();
                                            DateTime lastHeader = DateTime.MinValue;
                                            foreach (var t in tasks)
                                            {
                                                if (lastHeader != t.Date.Date)
                                                {
                                                    items.Add(t.Date.Date);
                                                    lastHeader = t.Date.Date;
                                                }

                                                items.Add(t);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    });

                    if (items == null || items.Count == 0)
                    {
                        if (hasSemester)
                        {
                            items = new List <object>()
                            {
                                PowerPlannerResources.GetString("String_NothingDue")
                            };
                        }
                        else if (isDisabledInSettings)
                        {
                            items = new List <object>()
                            {
                                PowerPlannerResources.GetString("String_WidgetDisabled")
                            };
                        }
                        else if (hasAccount)
                        {
                            items = new List <object>()
                            {
                                PowerPlannerResources.GetString("String_NoSemester")
                            };
                        }
                        else
                        {
                            items = new List <object>()
                            {
                                PowerPlannerResources.GetString("String_NoAccount")
                            };
                        }
                    }

                    _items = items;
                }
                catch (Exception ex)
                {
                    TelemetryExtension.Current?.TrackException(ex);
                }

                // Schedule next
                try
                {
                    if (tasks != null && tasks.Count > 0)
                    {
                        DateTime?nextChangeTime = GetNextAgendaChangeTime(tasks, _now);
                        if (nextChangeTime != null)
                        {
                            AlarmManagerHelper.Schedule(
                                context: _context,
                                receiverType: typeof(UpdateWidgetAgendaReceiver),
                                wakeTime: nextChangeTime.Value);
                        }
                    }
                }
                catch (Exception ex)
                {
                    TelemetryExtension.Current?.TrackException(ex);
                }
            }