0
0
Fork
You've already forked comaps
0
forked from comaps/comaps

[carplay] Keep the GPS fix alive while another nav app holds the CarPlay screen #15

Closed
eisa01 wants to merge 1 commit from carplay-location-keepalive into carplay-dashboard-mvp2
pull from: carplay-location-keepalive
merge into: eisa01:carplay-dashboard-mvp2
eisa01:main
eisa01:multi-surface-map-rendering
eisa01:private-testflight/2026-07-14
eisa01:carplay-dashboard-support
eisa01:modes-ui
eisa01:eisa01/carplay-road-shields
eisa01:carplay-0708
eisa01:forgejo-macos-runner
eisa01:create-venv
eisa01:carplay-0706
eisa01:carplay-0704-findings
eisa01:fable-review
eisa01:testflight/2026.06.30
eisa01:carplay-dashboard
eisa01:carplay-om-code-review
eisa01:carplay-code-review-jun28
eisa01:carplay-dashboard-mvp2
eisa01:car-follow-on-reacquire
eisa01:testflight/2026.06.20
eisa01:carplay-roundabout-exit-number
eisa01:carplay-destination
eisa01:carplay-dashboard-mvp
eisa01:name-cherry-picking
eisa01:om-pr-11243-2
eisa01:icu-cjk-fix
eisa01:simple-cjk-fix
eisa01:fix-crash-search
eisa01:scene-support
eisa01:om-carplay-button
eisa01:om-pr-10545
eisa01:submodule-main-branch
eisa01:improve-unit-testing-docs

Repro: launch CoMaps from CarPlay, switch the CarPlay screen to Apple Maps
and start navigation, switch to Google Maps and start navigation, wait a
couple of minutes, then switch back to CoMaps. CoMaps' CarPlay map has lost
its position fix and the blue dot never recovers — until the phone is
unlocked and the CoMaps phone app is foregrounded, at which point the fix
returns immediately.

Root cause (confirmed in code and in device/simulator logs that show both
CarPlay scene callbacks firing with activationState == 0 / .unattached):
the framework and location lifecycle were keyed off a CarPlay scene's
activationState, which UIKit reports unreliably. Three distinct gaps
combined to produce the symptom.

  1. Location was killed while the map was hosted on a CarPlay screen.
    When the phone is locked (or otherwise not foreground), the phone scene's
    sceneWillResignActive checked SceneLifecycle.isAnyCarSceneForegroundActive,
    which requires a CarPlay scene to report .foregroundActive. Because that
    state is unreliable (the app scene stays .foregroundInactive in the
    simulator and dashboard<->app handoffs race), the guard failed, the full
    resign path ran, keepRunningInBackground() returned NO (CoMaps itself was
    not routing), and location was stopped — logged as "Stop updating
    location" even though the map was on the car screen.

  2. Background location updates were never enabled for the CarPlay case.
    allowsBackgroundLocationUpdates was set only inside applicationWillResignActive,
    and keepRunningInBackground() only returned YES when CoMaps itself was
    routing or GPS-tracking. With the user navigating in Apple/Google Maps —
    not CoMaps — CoMaps was a fully backgrounded app with background updates
    disabled, so iOS suspended it and stopped delivering fixes; the cached
    fix went stale over the couple of minutes the other app held the screen.

  3. Location was never restarted on the CarPlay foreground path.
    carSceneDidBecomeActive() restored rendering and focus but never called
    applicationDidBecomeActive. Only the phone scene's sceneDidBecomeActive
    (re)started location — which is exactly why the fix recovered only after
    foregrounding the phone app, and not when switching back to CoMaps on
    CarPlay.

Fix, in three parts:

Part A — key the lifecycle on stable host state instead of activationState.

  • CarPlayService.isHostingMapOnCarScreen (mapHost == .carplay/.dashboard),
    which only flips when the map view is actually re-parented.
  • SceneLifecycle.shouldKeepForeground = phone scene foregroundActive
    (reliable for a normal app scene) OR isHostingMapOnCarScreen.
  • SceneDelegate.sceneWillResignActive keeps the framework and location
    alive based on isHostingMapOnCarScreen.
  • leaveForegroundIfNoSceneActive() runs the full background transition
    (onGetFocus(false) + applicationWillResignActive + EnterBackground) and
    is invoked from both carSceneDidEnterBackground and the end of
    CarPlayService.destroy(), so it is robust to UIKit skipping
    sceneDidEnterBackground for a disconnecting CarPlay scene (which would
    otherwise leave rendering and GPS running on a locked phone).
    This half mirrors the approach from the earlier, un-merged commit
    578b9ef2d2 on the carplay-background-host-state branch.

Part B — keep background location alive while CarPlay hosts the map.

  • keepRunningInBackground() now also returns YES while
    isHostingMapOnCarScreen, so CoMaps stays eligible for background
    location and is not suspended when another CarPlay nav app takes the
    screen with the phone locked.
  • The allowsBackgroundLocationUpdates policy is centralized in
    applyBackgroundLocationUpdatesPolicy and applied on BOTH lifecycle edges
    (applicationDidBecomeActive and applicationWillResignActive), not just on
    resign, so the CarPlay-hosted case enables background updates when
    location starts. The flag reverts to NO on CarPlay disconnect, when
    keepRunningInBackground() again returns NO.

Part C — restart location on the CarPlay foreground path.

  • carSceneDidBecomeActive() now also calls
    LocationManager.applicationDidBecomeActive(), mirroring the phone scene.
    This recovers the fix if the app was suspended while another nav app held
    the screen, and (with Part B) re-asserts the background-updates policy.
    Shared by both the CarPlay app and dashboard scene delegates.

Safety for phone-only users: when CarPlay is disconnected,
isHostingMapOnCarScreen is false and keepRunningInBackground() is unchanged,
so normal phone backgrounding still stops location when not routing — no
battery regression.

Also logs the framework EnterForeground/EnterBackground transitions and the
keep/leave-foreground decisions, which were previously invisible.

Verification requires a real device (the simulator cannot switch between
CarPlay apps and reports kCLErrorDenied artifacts): reproduce the sequence
above and confirm the blue dot is live the instant CoMaps returns to the
CarPlay screen without touching the phone; confirm location still stops on
CarPlay disconnect on a locked phone, and still stops on a plain phone lock
when not routing.

Generated with Claude Code Opus 4.8

Signed-off-by: eisa01 eisa01@gmail.com
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

Repro: launch CoMaps from CarPlay, switch the CarPlay screen to Apple Maps and start navigation, switch to Google Maps and start navigation, wait a couple of minutes, then switch back to CoMaps. CoMaps' CarPlay map has lost its position fix and the blue dot never recovers — until the phone is unlocked and the CoMaps phone app is foregrounded, at which point the fix returns immediately. Root cause (confirmed in code and in device/simulator logs that show both CarPlay scene callbacks firing with activationState == 0 / .unattached): the framework and location lifecycle were keyed off a CarPlay scene's activationState, which UIKit reports unreliably. Three distinct gaps combined to produce the symptom. 1. Location was killed while the map was hosted on a CarPlay screen. When the phone is locked (or otherwise not foreground), the phone scene's sceneWillResignActive checked SceneLifecycle.isAnyCarSceneForegroundActive, which requires a CarPlay scene to report .foregroundActive. Because that state is unreliable (the app scene stays .foregroundInactive in the simulator and dashboard<->app handoffs race), the guard failed, the full resign path ran, keepRunningInBackground() returned NO (CoMaps itself was not routing), and location was stopped — logged as "Stop updating location" even though the map was on the car screen. 2. Background location updates were never enabled for the CarPlay case. allowsBackgroundLocationUpdates was set only inside applicationWillResignActive, and keepRunningInBackground() only returned YES when CoMaps itself was routing or GPS-tracking. With the user navigating in Apple/Google Maps — not CoMaps — CoMaps was a fully backgrounded app with background updates disabled, so iOS suspended it and stopped delivering fixes; the cached fix went stale over the couple of minutes the other app held the screen. 3. Location was never restarted on the CarPlay foreground path. carSceneDidBecomeActive() restored rendering and focus but never called applicationDidBecomeActive. Only the phone scene's sceneDidBecomeActive (re)started location — which is exactly why the fix recovered only after foregrounding the phone app, and not when switching back to CoMaps on CarPlay. Fix, in three parts: Part A — key the lifecycle on stable host state instead of activationState. - CarPlayService.isHostingMapOnCarScreen (mapHost == .carplay/.dashboard), which only flips when the map view is actually re-parented. - SceneLifecycle.shouldKeepForeground = phone scene foregroundActive (reliable for a normal app scene) OR isHostingMapOnCarScreen. - SceneDelegate.sceneWillResignActive keeps the framework and location alive based on isHostingMapOnCarScreen. - leaveForegroundIfNoSceneActive() runs the full background transition (onGetFocus(false) + applicationWillResignActive + EnterBackground) and is invoked from both carSceneDidEnterBackground and the end of CarPlayService.destroy(), so it is robust to UIKit skipping sceneDidEnterBackground for a disconnecting CarPlay scene (which would otherwise leave rendering and GPS running on a locked phone). This half mirrors the approach from the earlier, un-merged commit 578b9ef2d2 on the carplay-background-host-state branch. Part B — keep background location alive while CarPlay hosts the map. - keepRunningInBackground() now also returns YES while isHostingMapOnCarScreen, so CoMaps stays eligible for background location and is not suspended when another CarPlay nav app takes the screen with the phone locked. - The allowsBackgroundLocationUpdates policy is centralized in applyBackgroundLocationUpdatesPolicy and applied on BOTH lifecycle edges (applicationDidBecomeActive and applicationWillResignActive), not just on resign, so the CarPlay-hosted case enables background updates when location starts. The flag reverts to NO on CarPlay disconnect, when keepRunningInBackground() again returns NO. Part C — restart location on the CarPlay foreground path. - carSceneDidBecomeActive() now also calls LocationManager.applicationDidBecomeActive(), mirroring the phone scene. This recovers the fix if the app was suspended while another nav app held the screen, and (with Part B) re-asserts the background-updates policy. Shared by both the CarPlay app and dashboard scene delegates. Safety for phone-only users: when CarPlay is disconnected, isHostingMapOnCarScreen is false and keepRunningInBackground() is unchanged, so normal phone backgrounding still stops location when not routing — no battery regression. Also logs the framework EnterForeground/EnterBackground transitions and the keep/leave-foreground decisions, which were previously invisible. Verification requires a real device (the simulator cannot switch between CarPlay apps and reports kCLErrorDenied artifacts): reproduce the sequence above and confirm the blue dot is live the instant CoMaps returns to the CarPlay screen without touching the phone; confirm location still stops on CarPlay disconnect on a locked phone, and still stops on a plain phone lock when not routing. Generated with Claude Code Opus 4.8 Signed-off-by: eisa01 <eisa01@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Repro: launch CoMaps from CarPlay, switch the CarPlay screen to Apple Maps
and start navigation, switch to Google Maps and start navigation, wait a
couple of minutes, then switch back to CoMaps. CoMaps' CarPlay map has lost
its position fix and the blue dot never recovers — until the phone is
unlocked and the CoMaps phone app is foregrounded, at which point the fix
returns immediately.
Root cause (confirmed in code and in device/simulator logs that show both
CarPlay scene callbacks firing with activationState == 0 / .unattached):
the framework and location lifecycle were keyed off a CarPlay scene's
activationState, which UIKit reports unreliably. Three distinct gaps
combined to produce the symptom.
1. Location was killed while the map was hosted on a CarPlay screen.
 When the phone is locked (or otherwise not foreground), the phone scene's
 sceneWillResignActive checked SceneLifecycle.isAnyCarSceneForegroundActive,
 which requires a CarPlay scene to report .foregroundActive. Because that
 state is unreliable (the app scene stays .foregroundInactive in the
 simulator and dashboard<->app handoffs race), the guard failed, the full
 resign path ran, keepRunningInBackground() returned NO (CoMaps itself was
 not routing), and location was stopped — logged as "Stop updating
 location" even though the map was on the car screen.
2. Background location updates were never enabled for the CarPlay case.
 allowsBackgroundLocationUpdates was set only inside applicationWillResignActive,
 and keepRunningInBackground() only returned YES when CoMaps itself was
 routing or GPS-tracking. With the user navigating in Apple/Google Maps —
 not CoMaps — CoMaps was a fully backgrounded app with background updates
 disabled, so iOS suspended it and stopped delivering fixes; the cached
 fix went stale over the couple of minutes the other app held the screen.
3. Location was never restarted on the CarPlay foreground path.
 carSceneDidBecomeActive() restored rendering and focus but never called
 applicationDidBecomeActive. Only the phone scene's sceneDidBecomeActive
 (re)started location — which is exactly why the fix recovered only after
 foregrounding the phone app, and not when switching back to CoMaps on
 CarPlay.
Fix, in three parts:
Part A — key the lifecycle on stable host state instead of activationState.
 - CarPlayService.isHostingMapOnCarScreen (mapHost == .carplay/.dashboard),
 which only flips when the map view is actually re-parented.
 - SceneLifecycle.shouldKeepForeground = phone scene foregroundActive
 (reliable for a normal app scene) OR isHostingMapOnCarScreen.
 - SceneDelegate.sceneWillResignActive keeps the framework and location
 alive based on isHostingMapOnCarScreen.
 - leaveForegroundIfNoSceneActive() runs the full background transition
 (onGetFocus(false) + applicationWillResignActive + EnterBackground) and
 is invoked from both carSceneDidEnterBackground and the end of
 CarPlayService.destroy(), so it is robust to UIKit skipping
 sceneDidEnterBackground for a disconnecting CarPlay scene (which would
 otherwise leave rendering and GPS running on a locked phone).
 This half mirrors the approach from the earlier, un-merged commit
 578b9ef2d2 on the carplay-background-host-state branch.
Part B — keep background location alive while CarPlay hosts the map.
 - keepRunningInBackground() now also returns YES while
 isHostingMapOnCarScreen, so CoMaps stays eligible for background
 location and is not suspended when another CarPlay nav app takes the
 screen with the phone locked.
 - The allowsBackgroundLocationUpdates policy is centralized in
 applyBackgroundLocationUpdatesPolicy and applied on BOTH lifecycle edges
 (applicationDidBecomeActive and applicationWillResignActive), not just on
 resign, so the CarPlay-hosted case enables background updates when
 location starts. The flag reverts to NO on CarPlay disconnect, when
 keepRunningInBackground() again returns NO.
Part C — restart location on the CarPlay foreground path.
 - carSceneDidBecomeActive() now also calls
 LocationManager.applicationDidBecomeActive(), mirroring the phone scene.
 This recovers the fix if the app was suspended while another nav app held
 the screen, and (with Part B) re-asserts the background-updates policy.
 Shared by both the CarPlay app and dashboard scene delegates.
Safety for phone-only users: when CarPlay is disconnected,
isHostingMapOnCarScreen is false and keepRunningInBackground() is unchanged,
so normal phone backgrounding still stops location when not routing — no
battery regression.
Also logs the framework EnterForeground/EnterBackground transitions and the
keep/leave-foreground decisions, which were previously invisible.
Verification requires a real device (the simulator cannot switch between
CarPlay apps and reports kCLErrorDenied artifacts): reproduce the sequence
above and confirm the blue dot is live the instant CoMaps returns to the
CarPlay screen without touching the phone; confirm location still stops on
CarPlay disconnect on a locked phone, and still stops on a plain phone lock
when not routing.
Generated with Claude Code Opus 4.8
Signed-off-by: eisa01 <eisa01@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author
Owner
Copy link

Incorporated in the WIP dashboard PR

Incorporated in the WIP dashboard PR
eisa01 closed this pull request 2026年06月28日 14:36:56 +02:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.
No reviewers
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
eisa01/comaps!15
Reference in a new issue
eisa01/comaps
No description provided.
Delete branch "carplay-location-keepalive"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?