DeviceMonitoringActivity - 10 minute threshold instantly breached

I'm seeing an issue where if I create and start a DeviceMonitoringActivitySchedule that

  • begins 1 minute in the future
  • lasts for 20 minutes
  • has a warning time of 1 minute
  • monitors a single app
  • registers a threshold of 10 minutes for that app's usage

the threshold is immediately breached upon the .startMonitoring call for that activity.

However if I repeat the exact same experiment but with the interval beginning in the same minute we're currently in (not 1 in the future), it works as expected - the threshold is breached after 10 minutes of using the app.

Is this expected behavior? If so why?

It's possible that you are using date components that the system is interpreting as an ongoing or "active" schedule (i.e. it's finding the previous time matching those date components), and the app you are monitoring has already accumulated 10 minutes of usage since the previous instance of the start components. Do you mind attaching a snippet of how you are creating your schedule?

        // stop monitoring existing activities
        let activityName = "10minuteUse"
        let relevantActivities = activities.filter({$0.rawValue.contains(activityName)})
        if (!relevantActivities.isEmpty) {
            deviceActivityCenter.stopMonitoring(relevantActivities)
        } 

        // generate details of new activity
        let intervalLengthInSeconds = 20 * 60 
        var sessionEvents = [DeviceActivityEvent.Name : DeviceActivityEvent]()
        sessionEvents[DeviceActivityEvent.Name(eventType: .sessionEnd)] = DeviceActivityEvent(applications: [token],
                                                                                              threshold: DateComponents(minute: 10))
        let intervalEnd = Date(timeIntervalSinceNow: TimeInterval(intervalLengthInSeconds))
        
        // this is what appears to be causing the threshold to be insta-breached.
        // using Date() here instead doesn't.
        let intervalStart = Date(timeIntervalSinceNow: 60)

        let schedule = DeviceActivitySchedule(intervalStart: Calendar.current.dateComponents([.hour, .minute], from: intervalStart),
                                              intervalEnd: Calendar.current.dateComponents([.day, .hour, .minute], from: intervalEnd),
                                              repeats: false,
                                              warningTime: DateComponents(minute: 1))
        let newActivity = DeviceActivityName(rawValue: activityName)

        // create new activity
        do {
            try deviceActivityCenter.startMonitoring(newActivity, during: schedule, events: sessionEvents)        
        } catch {
	          print("failed to start session: \(error.localizedDescription)") 
        }

The fact that you are specifying the .day component from intervalEnd is causing the threshold to fire immediately. When your app starts monitoring a schedule, the system will first check whether or not the schedule is currently "active". The way it does this is it tries to find the previous time the schedule started and ended. If the previous start time comes after the previous end time, then the system treats the schedule as "active" or "ongoing". Let's say that the current time is 1:00pm on Monday. Based on your snippet, the system will determine that the previous start time for the schedule was Sunday at 1:01pm and the previous end time was last Monday at 1:20pm. That means the schedule your app started monitoring has been active since Sunday at 1:01pm and will end at 1:20pm this Monday and will include all app usage that occurred between those two dates (which is why your threshold callback is being invoked immediately). In most cases, you probably want to keep the components for both the interval start and interval end parameters consistent.

TL;DR: If you remove .day from the following line, you should get the behavior you're looking for:

Calendar.current.dateComponents([.day, .hour, .minute], from: intervalEnd)

The documentation mentions this behavior about ongoing or "active" schedules here (although it could admittedly be a little more detailed):

Important

If the current date falls in between intervalStart and intervalEnd, the system calls the intervalDidStart(for:) method immediately upon starting to monitor the activity. If the current date doesn’t fall in between intervalStart and intervalEnd, then intervalDidStart(for:) calls at the next date matching intervalStart.

DeviceMonitoringActivity - 10 minute threshold instantly breached
 
 
Q