Skip to content

Count phone and watch app use in the daily analytics summaries - #2629

Merged
ps2 merged 2 commits into
next-devfrom
feature/daily-usage-analytics
Oct 30, 2026
Merged

ps2 merged 2 commits into
next-devfrom
feature/daily-usage-analytics

Conversation

@ps2

@ps2 ps2 commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Adds app-use counts to the daily analytics summary as a measure of management burden: how often Loop has to be opened, for how long, and how much is done in it. They ride on the existing once-a-day, out-of-session "Daily summary" event (#2574), so no new events are sent and no health data is included.

New summary properties

  • foregroundCount, foregroundSeconds, maxForegroundSeconds, and session-length buckets foregroundUnder10sCount, foreground10To60sCount, foreground1To5MinCount, foregroundOver5MinCount. A session runs from scene foreground to background, so Control Center or the notification shade don't split it.
  • touchCount: touch sequences in the app's window. Counted by a gesture recognizer on the window that fails on the first touch and never cancels or delays touches.
  • notificationOpenCount (default notification action), linkOpenCount (widget / Live Activity deep links), alertAcknowledgementCount (all acknowledgement paths: in-app, notification action, Watch, AlarmKit Stop).
  • screenCounts for status, bolus, carb entry and settings.
  • bolusCounts / carbEntryCounts by source (Phone, Watch, Remote) and overrideCounts by kind, each with a total (bolusCount, carbEntryCount, overrideCount).

Watch

  • The watch app keeps its own daily summary and sends each completed day to the phone with transferUserInfo (queued while the phone is out of reach; up to 14 unsent days kept). The phone logs it as a separate out-of-session "Watch daily summary" event with the watch's own day:
    • watchForegroundCount, watchForegroundSeconds, maxWatchForegroundSeconds and the same length buckets, from applicationDidBecomeActive to applicationWillResignActive (wrist down ends a session)
    • watchComplicationLaunchCount (launch userInfo carries CLKLaunchedTimelineEntryDateKey)
    • watchNotificationResponseCount
    • watchScreenCounts: carbEntry, bolus, presets, carbList, and swipes to the actions / chart / sensor pages
  • The phone's own summary adds watchContextRequestCount (the watch requests context on activation and when each page first appears, so this runs a little above the number of opens), watchAlertAcknowledgementCount and watchPresetCount.
  • Session-length bucketing moved to LoopCore (ForegroundSessions) so both share it; the phone's keys are unchanged. The phone no longer asserts on userInfo transfers from the watch.

Notes

  • The counts are stored as an optional nested value, so a summary saved by an earlier build still decodes and the day in progress isn't lost on upgrade.
  • PresetActivationObserver is now @MainActor; AnalyticsServicesManager is its only conformer and TemporaryPresetsManager already calls it on the main actor.
  • Not measured: lock-screen, Live Activity and watch-face complication glances (only complication taps).

Testing

  • New AnalyticsServicesManagerTests (6 tests: session bucketing, decoding a summary stored before this change, foreground/background with touches, background without foreground, action counts by source, watch counts) and WatchUsageRecorderTests (3 tests: a completed day is sent when the next starts, unsent days are kept until the session is available, userInfo round trip).
  • Simulator: tapped Settings, scrolled, went to the home screen; the stored summary showed one 44 s session, 2 touches, and status + settings screen counts, and Settings opened normally.
  • Watch side: builds, and the recorder logic is covered by the tests above; not yet run on a real watch, so the complication-launch detection in particular is unverified on hardware.

ps2 added 2 commits October 29, 2026 12:14
Adds management-burden counts to the daily summary so time spent in the app and how often it has to be opened can be measured, using the same once-a-day, out-of-session event as the existing loop and glucose counts:

- foregroundCount, foregroundSeconds, maxForegroundSeconds, and session lengths bucketed under 10 s, 10–60 s, 1–5 min and over 5 min, measured from scene foreground to background
- touchCount: touch sequences in the app's window, from a gesture recognizer that fails immediately and never cancels or delays touches
- notificationOpenCount, linkOpenCount (widget and Live Activity deep links) and alertAcknowledgementCount
- screenCounts for the status, bolus, carb entry and settings screens
- bolusCounts and carbEntryCounts by source (Phone, Watch, Remote) and overrideCounts by kind, with totals

The new counts are stored as an optional nested value so a summary saved by an earlier build still decodes and the day in progress is kept. PresetActivationObserver is now @mainactor; AnalyticsServicesManager is its only conformer and the presets manager already calls it on the main actor.
The watch app now keeps its own daily usage summary and sends each completed day to the phone with transferUserInfo, which queues while the phone is out of reach; up to 14 unsent days are kept. The phone logs it as a "Watch daily summary" event (out of session) carrying the watch's own day:

- watchForegroundCount, watchForegroundSeconds, maxWatchForegroundSeconds and the same session-length buckets as the phone, from applicationDidBecomeActive to applicationWillResignActive (wrist down ends a session)
- watchComplicationLaunchCount: launches whose userInfo carries CLKLaunchedTimelineEntryDateKey
- watchNotificationResponseCount
- watchScreenCounts: carb entry, bolus, presets, carb list, and swipes to the actions, chart and sensor pages

The phone's own daily summary adds watchContextRequestCount (the watch asks for context on activation and when each page first appears, so this runs a little above the number of opens), watchAlertAcknowledgementCount and watchPresetCount.

The session-length bucketing moves into LoopCore as ForegroundSessions so the phone and the watch share it; the phone's summary keys are unchanged. The phone no longer asserts on userInfo transfers from the watch.
@ps2 ps2 changed the title Count app use in the daily analytics summary Count phone and watch app use in the daily analytics summaries Oct 29, 2026
@ps2
ps2 merged commit e0eb985 into next-dev Oct 30, 2026
ps2 added a commit that referenced this pull request Oct 31, 2026
ExtensionDelegate.shared() looked up WKApplication.shared().delegate,
which is nil under the SwiftUI app lifecycle, so it trapped. The usage
recording added in #2629 calls it from SwiftUI views, so swiping
between pages, or opening carbs, bolus or presets, crashed the watch
app. The delegate now records itself when it is created.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant