Repository navigation
fix: re-block timed unblocks at the end registered with iOS - #106
Merged
Merged
Conversation
The window registered with DeviceActivity drops the fraction of a second from the end, but the stored end kept it. Since 1.6 the monitor re-blocks by comparing the stored end to now, so when intervalDidEnd arrived within that fraction the unblock still looked active and apps stayed open until the next heartbeat (:00 or :30) or app launch. Round the stored end down to a whole second, as the registered end already is, so the stored end is never after the registered end.
ykisana
force-pushed
the
fix/unblock-end-whole-second
branch
from
November 14, 2026 21:23
c813deb to
011f054
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A timed unblock can stay lifted for up to 30 minutes past its end.
DeviceActivityScheduleFactory.windowbuilds the interval from whole-second date components, so the end registered with iOS drops the fraction of a second.TimedUnblockServicestored the unshortened end, so for example 10:30:00.6 was saved while 10:30:00 was registered.ShieldResolverwhetherendDate > now. Before that, it re-blocked anytimedUnblock_activity as soon as its interval ended.intervalDidEndarrives inside that fraction of a second, the unblock still looks active. Nothing re-blocks until the heartbeat (:00 or :30), another callback, or opening Normal.This affects unblocks of 20 minutes or more. 15-minute unblocks are registered 1 s longer to meet the minimum interval, so they always end after the saved end.
Fix
Round the saved end down to a whole second, the same way the end registered with iOS already is, so the saved end is never after the registered end. What is registered with iOS doesn't change. The start is unchanged, so blocks made just before an unblock still lose to it.
Down rather than up: the saved interval is never longer than the chosen duration, so the countdown never starts above it, and 15-minute unblocks keep their registered end 1 s after the saved end.
Tests
timedUnblockAllStartedMidSecondReblocksAtItsRegisteredEndandgroupTimedUnblockStartedMidSecondReblocksAtItsRegisteredEnd. Each starts an unblock at 10:00:00.6 and reconciles at the end registered with the fakeDeviceActivityCenter. Both fail onmain: apps stay unblocked.customDurationSetsEndDateAndPersistsStartDatenow allows the end to fall up to 1 s before start + duration.Not verified
How often iOS actually delivers
intervalDidEndwithin the first second on a device. The callback log (Settings › Callback Log, debug builds) records each callback's time and shield change, so a 30-minute unblock on a debug build shows it.