Skip to content

Codex/background task#19

Open
tifroz wants to merge 5 commits into
skiptools:mainfrom
tifroz:codex/background-task
Open

Codex/background task#19
tifroz wants to merge 5 commits into
skiptools:mainfrom
tifroz:codex/background-task

Conversation

@tifroz

@tifroz tifroz commented Jun 10, 2026

Copy link
Copy Markdown

Thank you for contributing to the Skip project! Please use this space to describe your change and add any labels (bug, enhancement, documentation, etc.) to help categorize your contribution.

Please review the contribution guide at https://skip.dev/docs/contributing/ for advice and guidance on making high-quality PRs.

Skip Pull Request Checklist:

  • REQUIRED: I have signed the Contributor Agreement
  • REQUIRED: I have tested my change locally with swift test
  • OPTIONAL: I have tested my change on an iOS simulator or device
  • OPTIONAL: I have tested my change on an Android emulator or device

  • AI was used to generate or assist with generating this PR. Please specify below how you used AI to help you, and what steps you have taken to manually verify the changes.

Codex generated the code, manually validate from a sandbox native app


Summary

Adds three dual-platform device/app-shell capabilities to SkipDevice:

  • ApplicationRuntimeProvider for app lifecycle and memory pressure events
  • BackgroundActivity for finite user-visible background work
  • DeviceIdentity.current for basic platform device metadata

Changes

Application runtime events

Adds ApplicationRuntimeProvider, which exposes:

  • currentLifecyclePhase
  • monitorLifecycle() -> AsyncStream<ApplicationLifecycleEvent>
  • monitorMemoryPressure() -> AsyncStream<MemoryPressureEvent>
  • stop()

On Apple platforms this uses UIApplication notifications for lifecycle and memory warnings.

On Android this uses:

  • Application.ActivityLifecycleCallbacks for lifecycle
  • ComponentCallbacks2 for memory pressure

Android memory pressure maps onLowMemory, TRIM_MEMORY_RUNNING_CRITICAL, and TRIM_MEMORY_COMPLETE to .critical; other trim callbacks map to .warning.

Lifecycle events expose both:

  • kind, preserving iOS-style event names where possible
  • phase, a normalized app phase

Background activity

Adds BackgroundActivity.begin(_:) / BackgroundActivity.end(_:) for finite background work.

On iOS/tvOS this wraps UIApplication.beginBackgroundTask and endBackgroundTask.

On Android this adds a generic BackgroundActivityService foreground service with a registry of active activity IDs. The service stops when no activities remain.

Android foreground-service type mapping:

Reason Android foreground service type
localNetworkTransfer dataSync
mediaProcessing mediaProcessing on Android 15+, dataSync fallback
connectedDeviceTransfer connectedDevice
shortCriticalWork shortService when available, dataSync fallback

The Android service implements foreground-service timeout handling and stops promptly when Android reports a timeout.

Device identity

Adds DeviceIdentity.current, exposing low-level device metadata:

  • name
  • model
  • localizedModel
  • systemName
  • systemVersion
  • vendorIdentifier
  • manufacturer
  • brand
  • device
  • product

On Apple platforms this wraps UIDevice. On Android this reads from Build, Settings.Global, and Settings.Secure.

vendorIdentifier maps to UIDevice.identifierForVendor on Apple platforms and Settings.Secure.ANDROID_ID on Android.

Android setup

Adds an androidx.core:core dependency for NotificationCompat, ServiceCompat, and ContextCompat.

Updates README documentation with required Android manifest entries for apps using BackgroundActivity, including foreground-service permissions and service declaration.

Background task example usage

import SkipDevice

func runTransfer() async throws {
    let activityID = try await BackgroundActivity.begin(BackgroundActivityRequest(
        name: "Streaming to receiver",
        reason: .connectedDeviceTransfer,
        detail: "Maintaining the local transfer"
    ))

    do {
        try await streamToReceiver()
        await BackgroundActivity.end(activityID)
    } catch {
        await BackgroundActivity.end(activityID)
        throw error
    }
}

Background task in the sandbox

Notice the number of ticks executed in the background, from the UI report + logs

video-capture-20260610-143302-stream.mp4

@cla-bot cla-bot Bot added the cla-signed label Jun 10, 2026

@marcprux marcprux left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks useful overall! It does look like there's a concurrency issue in CI that we'll need to get fixed.

contents:
- block: 'dependencies'
contents:
- 'implementation("androidx.core:core:1.13.1")'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason to not use the latest (1.9.0) version of the dependency?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Android’s foreground-service docs say the ServiceCompat overload that accepts foreground-service types is available in androidx-core 1.12+

  • minimum defensible version: androidx.core:core:1.12.0
  • conservative current choice: androidx.core:core:1.13.1
  • latest stable if the Skip Android toolchain supports it: androidx.core:core:1.17.0

I picked 1.13.1 as a conservative stable version above that minimum. AndroidX’s release notes say 1.17.0 requires Kotlin Gradle Plugin 2.0.0+, so I’d prefer to only bump that far if skip-device’s Android build/toolchain is already aligned with that requirement.

Happy to update the PR to whichever version you prefer.

…. Reads full UIDevice data inside MainActor.assumeIsolated when safe, and returns a reduced Apple identity from background threads without touching UIKit.
@tifroz tifroz requested a review from marcprux June 11, 2026 01:56
@tifroz

tifroz commented Jun 23, 2026

Copy link
Copy Markdown
Author

@marcprux pinging your because it feels like PR that have a request change are falling off the radar? no rush

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants