Skip to content

feat(android): Add standalone app start tracing#5342

Merged
buenaflor merged 53 commits into
mainfrom
feat/standalone-app-start-tracing
Jun 12, 2026
Merged

feat(android): Add standalone app start tracing#5342
buenaflor merged 53 commits into
mainfrom
feat/standalone-app-start-tracing

Conversation

@buenaflor

@buenaflor buenaflor commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

📜 Description

Adds standalone Android app-start transaction support behind the opt-in enableStandaloneAppStartTracing option and the io.sentry.standalone-app-start-tracing.enable manifest key.

This PR also covers the non-activity startup path so cold starts triggered by broadcasts or foreground services can emit an App Start Cold/Warm transaction without waiting for an activity. The legacy flag-off behavior is preserved: activity app-start data remains nested under the ui.load transaction.

Additionally this PR adds a new attribute:

  • app.vitals.start.screen

💡 Motivation and Context

Resolves: #5046

Standalone app-start traces make app-start performance visible independently from activity load transactions, and they let Android report app starts where no activity is launched. The implementation keeps the default behavior unchanged and adds tests for the new option, manifest metadata, transaction context trace reuse, app-start metrics, event processing, and activity lifecycle behavior.

App-start creation paths

This PR preserves the existing flag-off behavior and adds a separate standalone app-start branch when enableStandaloneAppStartTracing is enabled.

Scenario How app start is created Resulting trace shape
Activity launch, flag ON ActivityLifecycleIntegration starts a standalone App Start transaction and the normal activity ui.load transaction on the same trace. Two sibling transactions: app.start for app-start phases, and ui.load for TTID/TTFD. The ui.load transaction does not contain an app.start.cold/warm child.
Activity launch, flag OFF Legacy activity lifecycle tracing creates app start as a child span of the activity transaction. One ui.load transaction with nested app.start.cold/warm, plus app-start phase spans under that child.
Non-activity process start, flag ON, Gradle plugin instrumentation present The Gradle plugin records Application.onCreate start/end through AppStartMetrics.onApplicationCreate/PostCreate. When no activity starts, ActivityLifecycleIntegration emits from that completed app-start span. One standalone App Start transaction and no ui.load; includes process.load and application.load phase spans.
Non-activity process start, flag ON, no Gradle plugin on API 35+ AppStartMetrics reads ApplicationStartInfo from ActivityManager to determine start type and use the platform APPLICATION_ONCREATE timestamp as the app-start end time. One standalone App Start transaction and no ui.load; includes process.load, but not application.load because the SDK does not have plugin instrumentation for that phase.
Non-activity process start, flag ON, no Gradle plugin on API <35 ApplicationStartInfo is unavailable, so AppStartMetrics defaults the no-activity process start to cold and uses the class-loaded timestamp fallback for the app-start end time. One standalone App Start transaction and no ui.load; includes process.load only and remains classified as cold.
Non-activity process start followed by activity The non-activity App Start transaction stores its trace ID for a later activity. The later activity ui.load reuses the stored trace ID and does not create a second standalone app-start transaction.
Non-activity process start, flag OFF The non-activity listener is not installed. Broadcasts/services emit no app-start transaction.

💚 How did you test it?

  • ./gradlew spotlessApply apiDump
  • Targeted unit coverage for the option, manifest metadata, app-start metric resolution, event processing, lifecycle integration, and transaction context trace reuse.
  • Manual end-to-end harness runs from scripts/test-standalone-app-start.sh, summarized below.

End-to-end Sentry verification

# What it tests Locally emitted shape Trace ID link
1a Launcher activity cold start, standalone flag ON. Standalone App Start emits alongside sibling MainActivity ui.load; shared trace ID; ui.load has no app.start.* child. app.start (app_start_cold=1156ms) + activity.load x2 + process.load + application.load; sibling ui.load (time_to_initial_display=1156ms) 52154e91...
1c Launcher activity cold start, standalone flag OFF. Legacy shape preserved: single ui.load with app.start.cold nested inside. ui.load (time_to_initial_display=1536ms) -> nested app.start.cold (app_start_cold=1537ms) -> process.load + activity.load x2 d3f0e049...
2a Broadcast cold, tier-1 end-time resolution with Gradle-plugin timing simulation. app.start (app_start_cold=1408ms) + process.load + application.load; no ui.load 81ae80f6...
2b Broadcast cold, tier-2 end-time resolution via Android 15 ApplicationStartInfo. app.start (app_start_cold=525ms) + process.load only; no ui.load 0dcd120e...
2c Broadcast cold, tier-3 fallback on API 33 using CLASS_LOADED_UPTIME_MS. app.start (app_start_cold=614ms) + process.load only; correctly classified Cold ac8a9189...
2d Foreground service cold start through the non-activity path. app.start (app_start_cold=4605ms) + process.load + application.load; no ui.load 905262ce...
2e Broadcast then launcher: trace reuse without duplicate standalone transaction. Same trace: broadcast app.start (app_start_cold=1751ms) + later sibling ui.load (time_to_initial_display=4563ms); no duplicate standalone 5da4a88f...
2f Broadcast + standalone flag OFF. No transaction emitted --

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

buenaflor and others added 10 commits April 2, 2026 14:43
Introduce experimental `enableStandaloneAppStartTracing` option that creates
a separate app start transaction instead of attaching app start as a child
span of the first activity transaction. This is the happy path only (foreground
importance, activity launch, first frame drawn as end time).

The standalone transaction shares the same trace ID as the activity transaction
but is not bound to the scope. App start measurements and child spans (process
init, content providers, application.onCreate) are attached to the standalone
transaction instead of the activity transaction.

Includes foreground importance check branching to prepare for the non-activity
launch path (next PR).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the app starts without launching an activity (service, broadcast
receiver, content provider), create a standalone app start transaction
with the end time determined by priority:

1. onApplicationPostCreate (Gradle plugin bytecode instrumentation)
2. ApplicationStartInfo timestamps (API 35+)
3. firstIdle - main thread idle handler (pre-API 35 fallback)

The non-activity app start transaction stores its trace ID so that if
an activity is later launched, the activity transaction reuses the same
trace ID to keep both in the same trace.

Adds OnNoActivityStartedListener callback from AppStartMetrics to
ActivityLifecycleIntegration, triggered by checkCreateTimeOnMain()
when no activity was created after Application.onCreate().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…entation

When an app is launched via broadcast receiver, service, or content provider
(no activity), detect this via Handler.post() and create a standalone app start
transaction. Resolves app start end time with priority: Gradle plugin >
ApplicationStartInfo (API 35+) > process init time. Also attaches child spans
(process init, content providers, Application.onCreate) to standalone
transactions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract the "try appStartSpan, fall back to sdkInitTimeSpan" logic used for
standalone (non-activity) app start transactions into a new
AppStartMetrics.getAppStartTimeSpanDirect() helper, removing the duplicated
inline fallback in ActivityLifecycleIntegration and the private helper in
PerformanceAndroidEventProcessor.

Also cache the API 35+ ApplicationStartInfo on registerLifecycleCallbacks so
onAppStartSpansSent no longer re-queries ActivityManager, and simplify the
non-activity detection path to always use the main-thread IdleHandler.

Regenerates the sentry-android-core API to include method additions missed in
prior commits on this branch (standalone-app-start options, trace id accessors,
OnNoActivityStartedListener).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires up the TestBroadcastReceiver added earlier so the sample app can trigger
a non-activity cold start via `adb shell am broadcast`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tart-tracing

# Conflicts:
#	sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java
…icate emission

Two pre-merge fixes for the standalone app-start tracing path introduced on
this branch (issue #5046):

- AppStartMetrics.checkCreateTimeOnMain() now defaults appStartType to COLD
  when UNKNOWN with no active activities. On API < 35 (where
  ApplicationStartInfo is unavailable) non-activity cold starts were stuck
  at UNKNOWN, which both misclassified the standalone transaction as
  App Start Warm and caused PerformanceAndroidEventProcessor.attachAppStartSpans
  to early-return (dropping process.load / application.load / contentprovider.load
  phase spans).

- ActivityLifecycleIntegration.onActivityPreCreated() now skips emitting a
  second standalone App Start transaction when the non-activity path has
  already reported the process's app start (detected via the stashed
  appStartTraceId). Previously a broadcast followed by an activity launch
  produced two standalone transactions (a spurious App Start Warm in addition
  to the broadcast's App Start Cold), violating one-per-process semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against aa9e0ef

@sentry

sentry Bot commented Apr 28, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.43.1 (1) release

⚙️ sentry-android Build Distribution Settings

@github-actions

github-actions Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 313.87 ms 370.44 ms 56.57 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
91bb874 314.47 ms 440.00 ms 125.53 ms
2195398 309.39 ms 354.53 ms 45.14 ms
9770665 315.64 ms 378.00 ms 62.36 ms
abfcc92 337.38 ms 427.39 ms 90.00 ms
b77456b 393.26 ms 441.10 ms 47.84 ms
8687935 332.52 ms 362.23 ms 29.71 ms
14ff5ee 419.75 ms 495.73 ms 75.98 ms
48277cd 320.38 ms 379.90 ms 59.52 ms
694d587 305.45 ms 378.38 ms 72.94 ms
4c04bb8 350.71 ms 413.63 ms 62.92 ms

App size

Revision Plain With Sentry Diff
91bb874 1.58 MiB 2.13 MiB 559.07 KiB
2195398 0 B 0 B 0 B
9770665 0 B 0 B 0 B
abfcc92 1.58 MiB 2.13 MiB 557.31 KiB
b77456b 1.58 MiB 2.12 MiB 548.11 KiB
8687935 1.58 MiB 2.19 MiB 619.17 KiB
14ff5ee 1.58 MiB 2.10 MiB 535.08 KiB
48277cd 0 B 0 B 0 B
694d587 1.58 MiB 2.19 MiB 620.06 KiB
4c04bb8 0 B 0 B 0 B

Previous results on branch: feat/standalone-app-start-tracing

Startup times

Revision Plain With Sentry Diff
1bfb17d 343.53 ms 407.87 ms 64.34 ms
6d3ea22 305.65 ms 342.00 ms 36.35 ms
97cc15e 349.13 ms 414.64 ms 65.51 ms
77721c1 401.33 ms 458.81 ms 57.48 ms
3bd6ab9 320.04 ms 362.42 ms 42.38 ms
7513741 320.19 ms 383.90 ms 63.71 ms
3c255ac 434.12 ms 545.04 ms 110.92 ms
04a4a5f 319.55 ms 374.56 ms 55.01 ms
21e3423 315.65 ms 363.04 ms 47.39 ms
f03cdee 325.54 ms 376.02 ms 50.48 ms

App size

Revision Plain With Sentry Diff
1bfb17d 0 B 0 B 0 B
6d3ea22 0 B 0 B 0 B
97cc15e 0 B 0 B 0 B
77721c1 0 B 0 B 0 B
3bd6ab9 0 B 0 B 0 B
7513741 0 B 0 B 0 B
3c255ac 0 B 0 B 0 B
04a4a5f 0 B 0 B 0 B
21e3423 0 B 0 B 0 B
f03cdee 0 B 0 B 0 B

buenaflor and others added 2 commits May 4, 2026 10:15
Rename the standalone app-start transaction to a single App Start name
so cold and warm starts group consistently while preserving the app.start op.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

🚨 Detected changes in high risk code 🚨

High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:

  • sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

🚨 Detected changes in high risk code 🚨

High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:

  • sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java

@github-actions

Copy link
Copy Markdown
Contributor

🚨 Detected changes in high risk code 🚨

High-risk code has higher potential to break the SDK and may be hard to test. To prevent severe bugs, apply the rollout process for releasing such changes and be extra careful when changing and reviewing these files:

  • sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java

buenaflor and others added 2 commits May 11, 2026 13:00
Co-authored-by: Cursor <cursoragent@cursor.com>
@buenaflor

buenaflor commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

Updated:

  • Added/kept the headless-to-ui.load trace connection behavior, including shared trace/sampleRand and a 1-minute cutoff for unrelated later activities -> if a headless app start and a ui.load is more than 1 minute apart then they won't be trace connected.
  • Fixed standalone app-start transaction origin so app.start uses auto.app.start, while ui.load remains auto.ui.activity

I also ran the E2E tests again described in the description

Comment thread standalone_app_start_report.md Outdated
buenaflor and others added 2 commits June 2, 2026 19:37
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the foreground headless guard from faking an observed activity so
late standalone app start init still lets the first real activity classify
startup and reset warm-start state correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep standalone app-start transactions open until activity lifecycle spans
are attached so early app-start completion does not drop activity spans.

Co-Authored-By: Cursor <cursoragent@cursor.com>
Comment thread sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ce39215. Configure here.

Comment thread CHANGELOG.md Outdated
…entry under Unreleased

Co-authored-by: Cursor <cursoragent@cursor.com>

@romtsn romtsn 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.

LGTM! great work

@runningcode runningcode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just some general comments on code, I'm not so familiar with app tracing though.

buenaflor and others added 2 commits June 10, 2026 13:12
- Use plain quotes for the "App Start" transaction name instead of {@code}
- Clarify that the API 35 gate refers to the device's runtime OS version

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nCreate start

START_TIMESTAMP_APPLICATION_ONCREATE is captured right before
Application.onCreate is invoked (ActivityThread.handleBindApplication),
so it is the onCreate start, not its end. Rename locals, fix comments
and javadoc accordingly, and drop the applicationOnCreate.setStoppedAt
branch which could have recorded a zero-length application.load span.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@buenaflor buenaflor merged commit 85fd8b1 into main Jun 12, 2026
68 checks passed
@buenaflor buenaflor deleted the feat/standalone-app-start-tracing branch June 12, 2026 12:42
lbloder pushed a commit that referenced this pull request Jun 12, 2026
* feat: Add standalone app start transaction (happy path)

Introduce experimental `enableStandaloneAppStartTracing` option that creates
a separate app start transaction instead of attaching app start as a child
span of the first activity transaction. This is the happy path only (foreground
importance, activity launch, first frame drawn as end time).

The standalone transaction shares the same trace ID as the activity transaction
but is not bound to the scope. App start measurements and child spans (process
init, content providers, application.onCreate) are attached to the standalone
transaction instead of the activity transaction.

Includes foreground importance check branching to prepare for the non-activity
launch path (next PR).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: Add non-activity app start path with end time resolution

When the app starts without launching an activity (service, broadcast
receiver, content provider), create a standalone app start transaction
with the end time determined by priority:

1. onApplicationPostCreate (Gradle plugin bytecode instrumentation)
2. ApplicationStartInfo timestamps (API 35+)
3. firstIdle - main thread idle handler (pre-API 35 fallback)

The non-activity app start transaction stores its trace ID so that if
an activity is later launched, the activity transaction reuses the same
trace ID to keep both in the same trace.

Adds OnNoActivityStartedListener callback from AppStartMetrics to
ActivityLifecycleIntegration, triggered by checkCreateTimeOnMain()
when no activity was created after Application.onCreate().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: Support non-activity app start tracing without bytecode instrumentation

When an app is launched via broadcast receiver, service, or content provider
(no activity), detect this via Handler.post() and create a standalone app start
transaction. Resolves app start end time with priority: Gradle plugin >
ApplicationStartInfo (API 35+) > process init time. Also attaches child spans
(process init, content providers, Application.onCreate) to standalone
transactions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: Consolidate non-activity app start time-span resolution

Extract the "try appStartSpan, fall back to sdkInitTimeSpan" logic used for
standalone (non-activity) app start transactions into a new
AppStartMetrics.getAppStartTimeSpanDirect() helper, removing the duplicated
inline fallback in ActivityLifecycleIntegration and the private helper in
PerformanceAndroidEventProcessor.

Also cache the API 35+ ApplicationStartInfo on registerLifecycleCallbacks so
onAppStartSpansSent no longer re-queries ActivityManager, and simplify the
non-activity detection path to always use the main-thread IdleHandler.

Regenerates the sentry-android-core API to include method additions missed in
prior commits on this branch (standalone-app-start options, trace id accessors,
OnNoActivityStartedListener).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(samples): Register TestBroadcastReceiver in manifest

Wires up the TestBroadcastReceiver added earlier so the sample app can trigger
a non-activity cold start via `adb shell am broadcast`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(app-start): resolve standalone tracing misclassification and duplicate emission

Two pre-merge fixes for the standalone app-start tracing path introduced on
this branch (issue #5046):

- AppStartMetrics.checkCreateTimeOnMain() now defaults appStartType to COLD
  when UNKNOWN with no active activities. On API < 35 (where
  ApplicationStartInfo is unavailable) non-activity cold starts were stuck
  at UNKNOWN, which both misclassified the standalone transaction as
  App Start Warm and caused PerformanceAndroidEventProcessor.attachAppStartSpans
  to early-return (dropping process.load / application.load / contentprovider.load
  phase spans).

- ActivityLifecycleIntegration.onActivityPreCreated() now skips emitting a
  second standalone App Start transaction when the non-activity path has
  already reported the process's app start (detected via the stashed
  appStartTraceId). Previously a broadcast followed by an activity launch
  produced two standalone transactions (a spurious App Start Warm in addition
  to the broadcast's App Start Cold), violating one-per-process semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(android): refine standalone app start tracing

* chore: Update generated files

* style(core): Apply spotless formatting

* changelog

* fix(android): Use stable app start transaction name

Rename the standalone app-start transaction to a single App Start name
so cold and warm starts group consistently while preserving the app.start op.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(android): Add standalone app start tracing

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Handle non-activity app starts below API 24

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Guard app start timestamp clock base

Co-authored-by: Cursor <cursoragent@cursor.com>

* ref(android): Remove app start reason plumbing

Co-authored-by: Cursor <cursoragent@cursor.com>

* ref(android): Clarify no-activity app start handling

Rename the private app start helper to reflect that it conditionally
handles non-activity starts. Keep comments and tests focused on behavior.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(android): Clarify non-activity app start fallback

Explain why unresolved non-activity starts default to cold when Activity
signals or ApplicationStartInfo classification are unavailable.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Preserve legacy no-activity app start guard

Only run the no-activity startup check for unresolved app starts or when
standalone app start tracing registered a listener. This keeps API 35
ApplicationStartInfo classifications from triggering legacy side effects.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* test(android): Opt into standalone no-activity API 35 tests

Register a no-op no-activity listener for API 35 end-time resolution tests
so they exercise the standalone path under the restored legacy guard.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Schedule no-activity idle check when standalone listener is set on API 35+

On API 35+, ApplicationStartInfo resolves appStartType before the
standalone app start listener is installed, causing the idle handler
condition to be false and skipping the no-activity detection entirely.

Register the idle handler from setOnNoActivityStartedListener when the
type is already resolved, ensuring onNoActivityStarted() fires for
standalone app start tracing on API 35+ devices.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ref(android): Remove dead foregroundImportance check in standalone app start path

The foregroundImportance guard was always true at that point because
appStartTime is only set to non-null inside the foregroundImportance
branch. Remove the redundant check and the misleading else comment
that described an unreachable code path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Prevent duplicate standalone app start measurements

Require the app-start pending flag even when standalone app-start transactions bypass foreground checks. Preserve completed non-activity app-start timings so fallback resolution does not overwrite stopped spans.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ref(android): Remove unused app start application context

Drop dead AppStartMetrics state that was assigned during lifecycle callback registration but never read.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ref(android): Rename getAppStartTimeSpanDirect to getAppStartTimeSpanForStandalone

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Do not set TTID/TTFD contributing flags on standalone app start spans

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Add volatile to noActivityStartedListener for cross-thread visibility

The field is written by setOnNoActivityStartedListener (called during
Sentry.init(), potentially on a background thread) and read on the main
thread in handleNoActivityStartIfNeededOnMain. Without volatile, the JMM
permits the main thread to see a stale null, silently skipping the
listener and preventing standalone app-start transaction creation.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Clear stale app start sampling decision in non-activity start path

onNoActivityStarted() did not clear the appStartSamplingDecision, which
could leak to the first ui.load transaction when an activity eventually
starts after a non-activity process launch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: Format adb test commands in TestBroadcastReceiver JavaDoc

Co-authored-by: Cursor <cursoragent@cursor.com>

* ref(android): Rename headless app start handling

Use headless terminology for app starts that do not reach an Activity and
schedule the headless check from lifecycle callback registration. This removes
listener setter side effects while preserving standalone app-start behavior.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Align foreground app start measurements

Use the foreground app start fallback for foreground standalone app
start transactions so measurements match the transaction timestamp.
Keep the headless-only span source limited to true headless starts.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Gate headless app start end time

Resolve the headless app start end timestamp only when standalone
headless tracing is active. This avoids stopping legacy app start
spans before a later foreground Activity can finish them.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(android): Update API 35 headless app start expectation

Make the ApplicationStartInfo headless test install the listener that
now gates headless end-time resolution, matching the standalone path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix headless app-start idle scheduling

* ref(android): Clarify headless app start state names

Rename private headless app start flags to distinguish the pending
main-thread check from the one-shot listener invocation guard. No
behavior change.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* ref(android): Use app.start origin for headless app start transaction

Set the standalone headless app start transaction origin to
`auto.app.start` instead of `auto.ui.activity`, which was semantically
incorrect for non-activity (broadcast/service/content provider) starts.

Also simplify the API 35+ ApplicationStartInfo onCreate timestamp
resolution by using the reported nanos directly as the uptime base.

Co-authored-by: Cursor <cursoragent@cursor.com>

* ref(android): refine standalone app start trace continuation

Drop the redundant trace-id sharing TransactionContext constructor; the
ui.load now shares the app.start trace solely through continueTrace.

Don't connect a headless app.start and a following activity's ui.load into
the same trace when they are more than 1 minute apart, since such a large
gap means they no longer belong to the same launch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): align headless app start tests with uptime-based onCreate timestamp

ApplicationStartInfo's START_TIMESTAMP_APPLICATION_ONCREATE is captured via
SystemClock.uptimeNanos(), the same base as TimeSpan, so no clock re-anchoring
is needed. Add the missing headless test setup (foreground-importance stubbing)
and fix the API 35 timestamp test to use uptime semantics.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(android): add standalone app start E2E harness

Wire the Android sample app for manual standalone app-start validation and add a reusable harness plus notes for the scenarios verified locally. Trim redundant comments around app-start trace continuation while keeping the non-obvious sampling and parentage details.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(android): remove standalone app start report

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(android): clarify app start transaction shapes

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(android): remove standalone app start harness

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Preserve app start activity counter

Keep the foreground headless guard from faking an observed activity so
late standalone app start init still lets the first real activity classify
startup and reset warm-start state correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(android): Finish app start after activity spans

Keep standalone app-start transactions open until activity lifecycle spans
are attached so early app-start completion does not drop activity spans.

Co-Authored-By: Cursor <cursoragent@cursor.com>

* feat(samples): enable standalone app start tracing and add headless-start broadcast receiver

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(changelog): resolve merge conflict and keep standalone app start entry under Unreleased

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(options): Clarify standalone app start javadoc per review

- Use plain quotes for the "App Start" transaction name instead of {@code}
- Clarify that the API 35 gate refers to the device's runtime OS version

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(android): Clarify ApplicationStartInfo onCreate timestamp marks onCreate start

START_TIMESTAMP_APPLICATION_ONCREATE is captured right before
Application.onCreate is invoked (ActivityThread.handleBindApplication),
so it is the onCreate start, not its end. Rename locals, fix comments
and javadoc accordingly, and drop the applicationOnCreate.setStoppedAt
branch which could have recorded a zero-length application.load span.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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.

feat: Send standalone app start transactions

5 participants