Skip to content

Instantly share code, notes, and snippets.

@SudoPlz
Last active May 6, 2026 23:04
Show Gist options
  • Select an option

  • Save SudoPlz/3598f86ba5600faac3f6051781ee4b83 to your computer and use it in GitHub Desktop.

Select an option

Save SudoPlz/3598f86ba5600faac3f6051781ee4b83 to your computer and use it in GitHub Desktop.
tti calculation scenarios

ANDROID TTI Launch Scenarios

Implementation Note

appOpenedTime always uses mainActivityStartTime: The difference between appStartedTimestamp (process creation) and mainActivityStartTime (MainActivity creation) is negligible (20-40ms). Using mainActivityStartTime consistently simplifies the code and fixes edge cases where the conditional logic couldn't distinguish between different launch scenarios (9c, 9f).


Scenarios Analysis

Scenario 1: Normal App Open (COLD START)

Description: User taps app icon → Fresh process starts → MainActivity instantiates

Result: COLD START

Visual Diagram:

                 Process (alive 1hr)            MainActivity                User
                    |                                |                        |
                    |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀 𝗮𝗽𝗽 𝗶𝗰𝗼𝗻
                    |                                |                        |
T+0ms              ┿━━━ Process starts              |                        |
                   |     𝗮𝗽𝗽𝗹𝗶𝗰𝗮𝘁𝗶𝗼𝗻𝗦𝘁𝗮𝗿𝘁𝗲𝗱=𝟬       |                        |
                   |                                |                        |
T+10ms             ├─ instantiateApplication()     |                        |
T+15ms             ├─ MainApplication.onCreate()    |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |  └─ Load RN, init modules      |                        |
                   |                                |                        |
T+120ms  🟢         ├─ instantiateActivity()         |                        |
         ┃          |                                |                        |
         ┃          |  ├─ endsWith(".MainActivity") = TRUE                    |
         ┃          |  ├─ markActivityInstantiation()                         |
         ┃          |  ├─ 𝘄𝗮𝘀𝗜𝗻𝗶𝘁𝗶𝗮𝗹𝗶𝘇𝗲𝗖𝗼𝗺𝗽𝗹𝗲𝘁𝗲=𝗳𝗮𝗹𝘀𝗲 (init still running)     |
         ┃          |  ├─ staticMainActivityCreationTime == null = TRUE       |
         ┃          |  └─ staticMainActivityCreationTime = 120                |
         ┃          |                                |                        |
T+120ms  ┃          |                                ●                        🖥️ Splash screen
         ┃          |                                |                           visible
         ┃          |                                |                        |
T+160ms  ┃          |                            onCreate()                   |
         ┃          |                                └─ API < 28 fallback not triggered
         ┃          |                                |                        |
         ┃          |                                ├─ Inflate views         |
         ┃          |                                ├─ Init React            |
         ┃          |                                |                        |
T+3500ms ┃          └─ initialize() complete         |                        |
         ┃                                           ├─ Content renders       |
         ┃                                           |                        |
         ┃                                           |                        |
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 120ms                                                ║
          ║ TTI = [view visible time] - 120ms                                    ║
          ╚══════════════════════════════════════════════════════════════════════╝


Scenario 2: Warm Start (Activity killed, process alive)

Description: App backgrounded → MainActivity destroyed & timestamp cleared → User returns → New MainActivity created.

Result: WARM START

Visual Diagram:

                 Process (alive 1hr)            MainActivity                User
                    |                                |                        |
T-3600000ms        |                                ●                        |
                   |                                | (created 1hr ago)      |
                   |                                |                        |
[App backgrounded - MainActivity destroyed by Android]
                   |                                |                        |
T-3599950ms        |                            onDestroy()                  |
                   |                                ├─ isChangingConfigurations = false
                   |                                └─ staticMainActivityCreationTime = null
                   |                                |                        |
                   |                                ✗ (destroyed)            |
                   |                                |                        |
[User taps app icon]
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀 𝗮𝗽𝗽 𝗶𝗰𝗼𝗻
                   |                                |                        |
T+0ms              |                                |                        |
T+10ms     🟢       ├─ instantiateActivity()         |                        |
           ┃        |  ├─ endsWith(".MainActivity") = TRUE                    |
           ┃        |  ├─ markActivityInstantiation()                         |
           ┃        |  ├─ 𝘄𝗮𝘀𝗜𝗻𝗶𝘁𝗶𝗮𝗹𝗶𝘇𝗲𝗖𝗼𝗺𝗽𝗹𝗲𝘁𝗲=𝘁𝗿𝘂𝗲 (init already done)       |
           ┃        |  ├─ staticMainActivityCreationTime == null = TRUE       |
           ┃        |  └─ 𝘀𝘁𝗮𝘁𝗶𝗰𝗠𝗮𝗶𝗻𝗔𝗰𝘁𝗶𝘃𝗶𝘁𝘆=𝟭𝟬                            |
           ┃        |                                |                        |
T+10ms     ┃        |                                ●                        🖥️ Splash screen
           ┃        |                                |                           visible
           ┃        |                                |                        |
T+60ms     ┃        |                            onCreate()                   |
           ┃        |                                |                        |
           ┃        |                                ├─ Inflate views         |
           ┃        |                                ├─ Init React            |
           ┃        |                                |                        |
T+3500ms   ┃        |                                ├─ Content renders       |
           ┃        |                                |                        |
           ┃        |                                |                        |
           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 10ms                                                 ║
          ║ TTI = [view visible time] - 10ms                                    ║
          ╚══════════════════════════════════════════════════════════════════════╝


Scenario 3: Share Menu Launch (Fresh Process)

Description: User shares from another app → ShareActivity (excluded) opens → User navigates to main app → MainActivity instantiates.

Result: WARM START

Visual Diagram:

                 Process (alive 1hr)            MainActivity                User
                    |                                |                        |
                    |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀 "𝗦𝗵𝗮𝗿𝗲"
                    |                                |                           𝗶𝗻 𝗮𝗻𝗼𝘁𝗵𝗲𝗿 𝗮𝗽𝗽
T+0ms              |                                |                        |
                   |     𝗮𝗽𝗽𝗹𝗶𝗰𝗮𝘁𝗶𝗼𝗻𝗦𝘁𝗮𝗿𝘁𝗲𝗱=𝟬       |                        |
                   |                                |                        |
T+15ms             ├─ MainApplication.onCreate()    |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |  └─ Load RN, init modules      |                        |
                   |                                |                        |
T+120ms            ├─ instantiateActivity()         |                        |
                   |  ├─ endsWith(".MainActivity") = FALSE                   |
                   |  └─ SKIPPED - No timestamp set |                        |
                   |                                |                        |
T+160ms            |                                |                        🖥️ Share sheet
                   |                                |                           visible
                   |                                |                        |
[User completes share, taps to open main app]
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀 𝘁𝗼 𝗼𝗽𝗲𝗻
                   |                                |                           𝗺𝗮𝗶𝗻 𝗮𝗽𝗽
                   |                                |                        |
T+5000ms   🟢       ├─ instantiateActivity()         |                        |
           ┃        |  ├─ endsWith(".MainActivity") = TRUE                    |
           ┃        |  ├─ markActivityInstantiation()                         |
           ┃        |  ├─ 𝘄𝗮𝘀𝗜𝗻𝗶𝘁𝗶𝗮𝗹𝗶𝘇𝗲𝗖𝗼𝗺𝗽𝗹𝗲𝘁𝗲=𝘁𝗿𝘂𝗲 (init done during share)    |
           ┃        |  ├─ staticMainActivityCreationTime == null = TRUE       |
           ┃        |  └─ 𝘀𝘁𝗮𝘁𝗶𝗰𝗠𝗮𝗶𝗻𝗔𝗰𝘁𝗶𝘃𝗶𝘁𝘆=𝟱𝟬𝟬𝟬                        |
           ┃        |                                |                        |
T+5000ms  ┃        |                                ●                        🖥️ Splash screen
           ┃        |                                |                           visible
           ┃        |                                |                        |
T+5050ms  ┃        |                            onCreate()                   |
           ┃        |                                |                        |
           ┃        |                                ├─ Inflate views         |
           ┃        |                                ├─ Init React            |
           ┃        |                                |                        |
T+8500ms  ┃        |                                ├─ Content renders       |
           ┃        |                                |                        |
           ┃        |                                |                        |
           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 5000ms                                               ║
          ║ TTI = [view visible time] - 5000ms                                   ║
          ╚══════════════════════════════════════════════════════════════════════╝


Scenario 4: Push Notification Launch (Headless → User Taps Later)

Description: Notification arrives → Service processes headlessly → User taps 1 minute later → MainActivity instantiates .

Result: WARM START

Visual Diagram:

                 Process (alive 1hr)            MainActivity                User
                    |                                |                        |
T+0ms              |                                |                        |
                   |     𝗮𝗽𝗽𝗹𝗶𝗰𝗮𝘁𝗶𝗼𝗻𝗦𝘁𝗮𝗿𝘁𝗲𝗱=𝟬       |                        |
                   |                                |                        |
T+15ms             ├─ MainApplication.onCreate()    |                        |
                   |                                |                        |
T+50ms             ├─ instantiateService()          |                        |
                   |  └─ markServiceInstantiation() |                        |
                   |  └─ serviceInstantiationBeforeActivity = true            |
                   |                                |                        |
T+80ms             ├─ MessagingService.onMessageReceived()                    |
                   |  (headless, no UI)             |                        |
                   |                                |                        |
[Process stays alive in background - no Activity]
                   |                                |                        |
                   |                                |                        |
                   |                                |                        |
[LATER: User taps notification]
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀
                   |                                |                           𝗻𝗼𝘁𝗶𝗳𝗶𝗰𝗮𝘁𝗶𝗼𝗻
                   |                                |                        |
T+60000ms  🟢       ├─ instantiateActivity()         |                        |
           ┃        |  ├─ endsWith(".MainActivity") = TRUE                    |
           ┃        |  ├─ markActivityInstantiation()                         |
           ┃        |  ├─ 𝘄𝗮𝘀𝗜𝗻𝗶𝘁𝗶𝗮𝗹𝗶𝘇𝗲𝗖𝗼𝗺𝗽𝗹𝗲𝘁𝗲=𝘁𝗿𝘂𝗲 (init done in background)  |
           ┃        |  ├─ staticMainActivityCreationTime == null = TRUE       |
           ┃        |  └─ 𝘀𝘁𝗮𝘁𝗶𝗰𝗠𝗮𝗶𝗻𝗔𝗰𝘁𝗶𝘃𝗶𝘁𝘆=𝟲𝟬𝟬𝟬𝟬                        |
           ┃        |                                |                        |
T+60000ms ┃        |                                ●                        🖥️ Splash screen
           ┃        |                                |                           visible
           ┃        |                                |                        |
T+60050ms┃        |                            onCreate()                   |
           ┃        |                                |                        |
           ┃        |                                ├─ Inflate views         |
           ┃        |                                ├─ Init React            |
           ┃        |                                |                        |
T+63500ms┃        |                                ├─ Content renders       |
           ┃        |                                |                        |
           ┃        |                                |                        |
           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 60000ms                                              ║
          ║ TTI = [view visible time] - 60000ms                                  ║
          ╚══════════════════════════════════════════════════════════════════════╝

Scenario 5: Push Notification with Immediate Activity Launch

Description: Notification arrives → Service instantiates → User taps notification immediately (before initialize() finishes) → MainActivity opens → User waits for initialize() to complete.

Result: COLD START

Visual Diagram:

                 Process (alive 1hr)            MainActivity                User
                    |                                |                        |
                    |                                |                        📱 𝗡𝗼𝘁𝗶𝗳𝗶𝗰𝗮𝘁𝗶𝗼𝗻 𝗮𝗿𝗿𝗶𝘃𝗲𝘀
                    |                                |                        |
T+0ms              |                                |                        |
                   |     𝗮𝗽𝗽𝗹𝗶𝗰𝗮𝘁𝗶𝗼𝗻𝗦𝘁𝗮𝗿𝘁𝗲𝗱=𝟬       |                        |
                   |                                |                        |
T+15ms             ├─ MainApplication.onCreate()    |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |  └─ Load RN, init modules      |                        |
                   |                                |                        |
T+80ms             ├─ instantiateService()          |                        |
                   |  └─ serviceInstantiationBeforeActivity = true            |
                   |                                |                        |
T+100ms            ├─ MessagingService.onMessageReceived()                    |
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀 𝗻𝗼𝘁𝗶𝗳
                   |                                |                           𝗶𝗺𝗺𝗲𝗱𝗶𝗮𝘁𝗲𝗹𝘆
                   |                                |                        |
T+120ms   🟢        ├─ instantiateActivity()         |                        |
           ┃        |  ├─ endsWith(".MainActivity") = TRUE                    |
           ┃        |  ├─ markActivityInstantiation()                         |
           ┃        |  ├─ 𝘄𝗮𝘀𝗜𝗻𝗶𝘁𝗶𝗮𝗹𝗶𝘇𝗲𝗖𝗼𝗺𝗽𝗹𝗲𝘁𝗲=𝗳𝗮𝗹𝘀𝗲 (init still running!)  |
           ┃        |  ├─ staticMainActivityCreationTime == null = TRUE       |
           ┃        |  └─ 𝘀𝘁𝗮𝘁𝗶𝗰𝗠𝗮𝗶𝗻𝗔𝗰𝘁𝗶𝘃𝗶𝘁𝘆=𝟭𝟮𝟬                        |
           ┃        |                                |                        |
T+120ms   ┃        |                                ●                        🖥️ Splash screen
           ┃        |                                |                           visible
           ┃        |                                |                        |
T+160ms   ┃        |                            onCreate()                   |
           ┃        |                                |                        |
           ┃        |                                ├─ Inflate views         |
           ┃        |                                ├─ Init React            |
           ┃        |                                |                        |
T+3500ms  ┃        └─ initialize() complete         |                        |
           ┃                                         ├─ Content renders       |
           ┃                                         |                        |
           ┃                                         |                        |
           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 120ms                                                ║
          ║ appStartType = COLD (initialize not complete when MA opened)          ║
          ║ TTI = [view visible time] - 120ms = ~3380ms                          ║
          ╚══════════════════════════════════════════════════════════════════════╝

Scenario 6: Foreground Notification Tap (MainActivity Already Running)

Description: App already running → User taps notification → onNewIntent() called (not instantiateActivity) → Duplicate detection prevents TTI measurement (no new activity creation).

Result: NO TTI CALCULATED (duplicate detection)

Visual Diagram:

                 Process (alive)                 MainActivity                User
                    |                                ●                        |
T-10000ms          |                      staticMainActivityCreationTime      |
                   |                            set previously                |
                   |                                |                        |
                   |                                |                        |
                   |                                |                        📱 𝗡𝗼𝘁𝗶𝗳𝗶𝗰𝗮𝘁𝗶𝗼𝗻 𝗮𝗿𝗿𝗶𝘃𝗲𝘀
                   |                                |                        |
T+0ms             |                                |                        |
                   ├─ Notification arrives         |                        |
                   |                                |                        |
T+10ms            ├─ MessagingService.onMessageReceived()                    |
                   |  └─ serviceInstantiationBeforeActivity = false          |
                   |                                |                        |
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀
                   |                                |                           𝗻𝗼𝘁𝗶𝗳𝗶𝗰𝗮𝘁𝗶𝗼𝗻
T+5000ms          |                                |                        |
                   |                                |                        |
T+5010ms          |                            onNewIntent()                |
                   |                                |                        |
                   |                                ├─ instantiateActivity() NOT called
                   |                                ├─ staticMainActivityCreationTime unchanged
                   |                                |                        |
T+5020ms          |                            𝗴𝗲𝘁𝗔𝗽𝗽𝗨𝗜𝗩𝗶𝗲𝘄𝗲𝗱=𝘁𝗿𝘂𝗲       |
                   |                                |                        |
                   |                                ├─ 𝗡𝗼 𝗮𝗻𝗮𝗹𝘆𝘁𝗶𝗰𝘀 𝘀𝗲𝗻𝘁   |
                   |                                |                        |

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ NO TTI MEASUREMENT (duplicate detection blocks it)                   ║
          ║ appViewedByMainActivityTimes[OLD TIME] = true → returns TRUE         ║
          ║ No analytics sent → Correct behavior                                ║
          ╚══════════════════════════════════════════════════════════════════════╝

Scenario 7: Configuration Change (Screen Rotation)

Description: App running → User rotates device → onConfigurationChanged() called (activity not destroyed/recreated due to android:configChanges) → No TTI measurement (no lifecycle change).

Result: NO TTI CALCULATED (no activity recreation)

Visual Diagram:

                 Process (alive)                 MainActivity                User
                    |                                ●                        |
                   |                      staticMainActivityCreationTime      |
                   |                            unchanged                     |
                   |                                |                        |
                   |                                |                        🔄 𝗨𝘀𝗲𝗿 𝗿𝗼𝘁𝗮𝘁𝗲𝘀
                   |                                |                           𝗱𝗲𝘃𝗶𝗰𝗲
T+0ms             |                                |                        |
                   |                                |                        |
T+10ms            |                      onConfigurationChanged()            |
                   |                                |                        |
                   |                                ├─ instantiateActivity() NOT called
                   |                                ├─ onDestroy() NOT called
                   |                                ├─ staticMainActivityCreationTime unchanged
                   |                                |                        |
T+50ms            |                            Redraws view                  🖥️ New orientation

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ NO TTI MEASUREMENT (no activity lifecycle change)                    ║
          ║ android:configChanges prevents activity recreation                   ║
          ║ No impact on TTI tracking → Correct behavior                        ║
          ╚══════════════════════════════════════════════════════════════════════╝

Scenario 8: IncomingCallActivity Launch

Description: Incoming call → MainActivity opens first → IncomingCallActivity may appear as overlay/system UI (excluded) → User answers → MainActivity continues.

Result: COLD or WARM START ✅ (depends on whether initialize() finished during call)

Visual Diagram:

                 Process (NEW)                   MainActivity                User
                    |                                |                        |
                    |                                |                        📞 𝗜𝗻𝗰𝗼𝗺𝗶𝗻𝗴 𝗰𝗮𝗹𝗹
                    |                                |                        |
T+0ms             |                                |                        |
                   𝗮𝗽𝗽𝗹𝗶𝗰𝗮𝘁𝗶𝗼𝗻𝗦𝘁𝗮𝗿𝘁𝗲𝗱=𝟬              |                        |
                   |                                |                        |
T+15ms            ├─ MainApplication.onCreate()    |                        |
                   |                                |                        |
T+120ms  🟢         ├─ instantiateActivity()         |                        |
         ┃          |  ├─ endsWith(".MainActivity") = TRUE                    |
         ┃          |  ├─ markActivityInstantiation()                         |
         ┃          |  ├─ 𝘄𝗮𝘀𝗜𝗻𝗶𝘁𝗶𝗮𝗹𝗶𝘇𝗲𝗖𝗼𝗺𝗽𝗹𝗲𝘁𝗲=𝘁𝗿𝘂𝗲/𝗳𝗮𝗹𝘀𝗲 (COLD/WARM)      |
         ┃          |  ├─ staticMainActivityCreationTime == null = TRUE       |
         ┃          |  └─ 𝘀𝘁𝗮𝘁𝗶𝗰𝗠𝗮𝗶𝗻𝗔𝗰𝘁𝗶𝘃𝗶𝘁𝘆=𝟭𝟮𝟬                        |
         ┃          |                                |                        |
T+120ms  ┃          |                                ●                        🖥️ Splash screen
         ┃          |                                |                           visible
         ┃          |                                |                        |
T+160ms  ┃          |                            onCreate()                   |
         ┃          |                                |                        |
         ┃          |                                ├─ Inflate views         |
         ┃          |                                ├─ Init React            |
                   |                                |                        |
                   |   (IncomingCallActivity may    |                        |
                   |    appear as overlay/system    |                        |
                   |    UI, not tracked in TTI)     |                        |
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝗮𝗻𝘀𝘄𝗲𝗿𝘀 𝗰𝗮𝗹𝗹
                   |                                |                        |
T+3500ms ┃          └─ initialize() complete         |                        |
         ┃                                           ├─ Content renders       |
         ┃                                           |                        |
         ┃                                           |                        |
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appStartType = COLD/WARM (depends on initialize() completion)        ║
          ║ appOpenedTime = 120ms                                                ║
          ║ TTI = [view visible time] - 120ms                                    ║
          ║ IncomingCallActivity (overlay/system UI) correctly EXCLUDED          ║
          ╚══════════════════════════════════════════════════════════════════════╝


Scenario 9: App Icon Long Press

Description: User long-presses app icon → Process starts (on some phones, e.g., Samsung) or doesn't start (on others, e.g., Google Pixel) → Shortcuts menu appears → Various user actions follow.

9a: Long-press → Tap Shortcut (COLD)

Description: User long-presses → Process starts → Shortcuts menu appears → User taps a shortcut option → MainActivity created with discord_shortcut Intent extra.

Result: COLD START

Visual Diagram:

                 Process (NEW)                   MainActivity                User
                    |                                |                        |
                    |                                |                        👆 𝗨𝘀𝗲𝗿 𝗹𝗼𝗻𝗴 𝗽𝗿𝗲𝘀𝘀𝗲𝘀 𝗶𝗰𝗼𝗻
                    |                                |                           (Samsung: triggers process start;
                    |                                |                            Pixel: process doesn't start)
                    |                                |                        |
T+0ms              ┿━━━ Process starts              |                        |
                   |     (Samsung: starts here;      |                        |
                   |      Pixel: starts later when   |                        |
                   |      user taps shortcut)         |                        |
                   |     𝗮𝗽𝗽𝗹𝗶𝗰𝗮𝘁𝗶𝗼𝗻𝗦𝘁𝗮𝗿𝘁𝗲𝗱=𝟬       |                        |
                   |                                |                        |
T+15ms             ├─ MainApplication.onCreate()    |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |                                |                        |
T+100ms            |                                |                        🖥️ Shortcuts menu appears
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀 𝘀𝗵𝗼𝗿𝘁𝗰𝘂𝘁
                   |                                |                        |
T+120ms  🟢         ├─ instantiateActivity()         |                        |
         ┃          |  ├─ Intent has discord_shortcut extra                  |
         ┃          |  ├─ wasLaunchedViaShortcut = true                      |
         ┃          |  ├─ wasInitializeComplete = false (COLD)                |
         ┃          |  └─ staticMainActivityCreationTime = 120                |
         ┃          |                                |                        |
T+120ms  ┃          |                                ●                        🖥️ Splash screen
         ┃          |                                |                           visible
         ┃          |                                |                        |
T+160ms  ┃          |                            onCreate()                   |
         ┃          |                                |                        |
         ┃          |                                ├─ Inflate views         |
         ┃          |                                ├─ Init React            |
         ┃          |                                |                        |
T+3500ms ┃          └─ initialize() complete         |                        |
         ┃                                           ├─ Content renders       |
         ┃                                           |                        |
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 120ms                                                ║
          ║ TTI = [view visible time] - 120ms                                    ║
          ╚══════════════════════════════════════════════════════════════════════╝

9b: Long-press → Tap Shortcut (WARM)

Description: User long-presses → Process starts → User waits/browses shortcuts → User taps shortcut after initialize() completes.

Result: WARM START

Visual Diagram:

                 Process (NEW)                   MainActivity                User
                    |                                |                        |
                    |                                |                        👆 𝗨𝘀𝗲𝗿 𝗹𝗼𝗻𝗴 𝗽𝗿𝗲𝘀𝘀𝗲𝘀 𝗶𝗰𝗼𝗻
                    |                                |                           (Samsung: process starts here;
                    |                                |                            Pixel: process doesn't start)
                    |                                |                        |
T+0ms              ┿━━━ Process starts              |                        |
                   |     (Samsung only; Pixel:        |                        |
                   |      starts later when          |                        |
                   |      user taps shortcut)        |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |                                |                        |
T+100ms            |                                |                        🖥️ Shortcuts menu appears
                   |                                |                        |
                   |                                |                        ⏱️ 𝗨𝘀𝗲𝗿 𝘄𝗮𝗶𝘁𝘀
                   |                                |                        |
T+3500ms           └─ initialize() complete         |                        |
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀 𝘀𝗵𝗼𝗿𝘁𝗰𝘂𝘁
                   |                                |                        |
T+4000ms  🟢         ├─ instantiateActivity()         |                        |
         ┃          |  ├─ wasLaunchedViaShortcut = true                      |
         ┃          |  ├─ wasInitializeComplete = true (WARM)                |
         ┃          |  └─ staticMainActivityCreationTime = 4000                |
         ┃          |                                |                        |
T+4000ms ┃          |                                ●                        🖥️ Splash screen
         ┃          |                                |                           visible
         ┃          |                                |                        |
T+4100ms┃          |                                ├─ Content renders       |
         ┃          |                                |                        |
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 4000ms                                               ║
          ║ TTI = [view visible time] - 4000ms                                    ║
          ╚══════════════════════════════════════════════════════════════════════╝

9c: Long-press → Tap Regular Icon (COLD) - RARE

Description: User long-presses → Process starts → Shortcuts menu appears → User dismisses menu or taps regular icon within 100ms after the shortcuts show up, instead of tapping one of the shortcuts → MainActivity created by tapping app icon (no discord_shortcut extra).

Result: COLD START

Visual Diagram:

                 Process (NEW)                   MainActivity                User
                    |                                |                        |
                    |                                |                        👆 𝗨𝘀𝗲𝗿 𝗹𝗼𝗻𝗴 𝗽𝗿𝗲𝘀𝘀𝗲𝘀 𝗶𝗰𝗼𝗻
                    |                                |                           (Samsung: triggers process start;
                    |                                |                            Pixel: process doesn't start)
                    |                                |                        |
T+0ms              ┿━━━ Process starts              |                        |
                   |     (Samsung: starts here;      |                        |
                   |      Pixel: starts later when   |                        |
                   |      user taps icon)             |                        |
                   |     𝗮𝗽𝗽𝗹𝗶𝗰𝗮𝘁𝗶𝗼𝗻𝗦𝘁𝗮𝗿𝘁𝗲𝗱=𝟬       |                        |
                   |                                |                        |
T+15ms             ├─ MainApplication.onCreate()    |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |                                |                        |
T+100ms            |                                |                        🖥️ Shortcuts menu appears
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀 𝗿𝗲𝗴𝘂𝗹𝗮𝗿 𝗶𝗰𝗼𝗻
                   |                                |                           (no discord_shortcut extra)
                   |                                |                        |
T+120ms  🟢         ├─ instantiateActivity()         |                        |
         ┃          |  ├─ Intent has NO discord_shortcut extra               |
         ┃          |  ├─ wasLaunchedViaShortcut = false                     |
         ┃          |  ├─ wasInitializeComplete = false (COLD)                |
         ┃          |  └─ staticMainActivityCreationTime = 120                |
         ┃          |                                |                        |
T+120ms  ┃          |                                ●                        🖥️ Splash screen
         ┃          |                                |                           visible
         ┃          |                                |                        |
T+160ms  ┃          |                            onCreate()                   |
         ┃          |                                |                        |
         ┃          |                                ├─ Inflate views         |
         ┃          |                                ├─ Init React            |
         ┃          |                                |                        |
T+3500ms ┃          └─ initialize() complete         |                        |
         ┃                                           ├─ Content renders       |
         ┃                                           |                        |
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 120ms                                                ║
          ║ TTI = [view visible time] - 120ms                                    ║
          ╚══════════════════════════════════════════════════════════════════════╝

9d: Long-press → Tap Regular Icon (WARM)

Description: User long-presses → Process starts → User waits → User taps app icon icon after initialize() completes.

Result: WARM START

Visual Diagram:

                 Process (NEW)                   MainActivity                User
                    |                                |                        |
                    |                                |                        👆 𝗨𝘀𝗲𝗿 𝗹𝗼𝗻𝗴 𝗽𝗿𝗲𝘀𝘀𝗲𝘀 𝗶𝗰𝗼𝗻
                    |                                |                           (Samsung: process starts here;
                    |                                |                            Pixel: process doesn't start)
                    |                                |                        |
T+0ms              ┿━━━ Process starts              |                        |
                   |     (Samsung only; Pixel:       |                        |
                   |      starts later when          |                        |
                   |      user taps icon)            |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |                                |                        |
T+100ms            |                                |                        🖥️ Shortcuts menu appears
                   |                                |                        |
                   |                                |                        ⏱️ 𝗨𝘀𝗲𝗿 𝘄𝗮𝗶𝘁𝘀
                   |                                |                        |
T+3500ms           └─ initialize() complete         |                        |
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀 𝗿𝗲𝗴𝘂𝗹𝗮𝗿 𝗶𝗰𝗼𝗻
                   |                                |                        |
T+4000ms  🟢         ├─ instantiateActivity()         |                        |
         ┃          |  ├─ Intent has NO discord_shortcut extra               |
         ┃          |  ├─ wasLaunchedViaShortcut = false                     |
         ┃          |  ├─ wasInitializeComplete = true (WARM)                |
         ┃          |  └─ staticMainActivityCreationTime = 4000                |
         ┃          |                                |                        |
T+4000ms ┃          |                                ●                        🖥️ Splash screen
         ┃          |                                |                           visible
         ┃          |                                |                        |
T+4100ms┃          |                                ├─ Content renders       |
         ┃          |                                |                        |
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 4000ms                                               ║
          ║ TTI = [view visible time] - 4000ms                                    ║
          ╚══════════════════════════════════════════════════════════════════════╝

9e: Long-press → Notification Tap

Description: User long-presses → Process starts → User taps notification → Service/receiver created before MainActivity.

Result: COLD or WARM START

Visual Diagram:

                 Process (NEW)                   MainActivity                User
                    |                                |                        |
                    |                                |                        👆 𝗨𝘀𝗲𝗿 𝗹𝗼𝗻𝗴 𝗽𝗿𝗲𝘀𝘀𝗲𝘀 𝗶𝗰𝗼𝗻
                    |                                |                           (Samsung: process starts here;
                    |                                |                            Pixel: process doesn't start)
                    |                                |                        |
T+0ms              ┿━━━ Process starts              |                        |
                   |     (Samsung only; Pixel:       |                        |
                   |      starts later when          |                        |
                   |      user taps notification)    |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |                                |                        |
T+100ms            |                                |                        🖥️ Shortcuts menu appears
                   |                                |                        |
                   |                                |                        📱 𝗡𝗼𝘁𝗶𝗳𝗶𝗰𝗮𝘁𝗶𝗼𝗻 𝗮𝗿𝗿𝗶𝘃𝗲𝘀
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘁𝗮𝗽𝘀 𝗻𝗼𝘁𝗶𝗳𝗶𝗰𝗮𝘁𝗶𝗼𝗻
                   |                                |                        |
T+5000ms           ├─ instantiateService()          |                        |
                   |  └─ serviceInstantiationBeforeActivity = true            |
                   |                                |                        |
T+5010ms  🟢         ├─ instantiateActivity()         |                        |
         ┃          |  ├─ endsWith(".MainActivity") = TRUE                    |
         ┃          |  ├─ markActivityInstantiation()                         |
         ┃          |  ├─ 𝘄𝗮𝘀𝗜𝗻𝗶𝘁𝗶𝗮𝗹𝗶𝘇𝗲𝗖𝗼𝗺𝗽𝗹𝗲𝘁𝗲=𝘁𝗿𝘂𝗲/𝗳𝗮𝗹𝘀𝗲 (COLD/WARM)      |
         ┃          |  ├─ staticMainActivityCreationTime == null = TRUE       |
         ┃          |  └─ 𝘀𝘁𝗮𝘁𝗶𝗰𝗠𝗮𝗶𝗻𝗔𝗰𝘁𝗶𝘃𝗶𝘁𝘆=𝟱𝟬𝟭𝟬                        |
         ┃          |                                |                        |
T+5010ms ┃          |                                ●                        🖥️ Splash screen
         ┃          |                                |                           visible
         ┃          |                                |                        |
T+5060ms┃          |                            onCreate()                   |
         ┃          |                                |                        |
         ┃          |                                ├─ Inflate views         |
         ┃          |                                ├─ Init React            |
         ┃          |                                |                        |
T+8510ms┃          |                                ├─ Content renders       |
         ┃          |                                |                        |
         ┃          |                                |                        |
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 5010ms                                               ║
          ║ TTI = [view visible time] - 5010ms                                    ║
          ╚══════════════════════════════════════════════════════════════════════╝

9f: Long-press → Share Activity

Description: User long-presses → Process starts → User shares from another app → ShareActivity created before MainActivity.

Result: WARM START

Visual Diagram:

                 Process (NEW)                   MainActivity                User
                    |                                |                        |
                    |                                |                        👆 𝗨𝘀𝗲𝗿 𝗹𝗼𝗻𝗴 𝗽𝗿𝗲𝘀𝘀𝗲𝘀 𝗶𝗰𝗼𝗻
                    |                                |                           (Samsung: process starts here;
                    |                                |                            Pixel: process doesn't start)
                    |                                |                        |
T+0ms              ┿━━━ Process starts              |                        |
                   |     (Samsung only; Pixel:       |                        |
                   |      starts later when          |                        |
                   |      user shares from app)      |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |                                |                        |
T+100ms            |                                |                        🖥️ Shortcuts menu appears
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝘀𝗵𝗮𝗿𝗲𝘀 𝗳𝗿𝗼𝗺 𝗮𝗻𝗼𝘁𝗵𝗲𝗿 𝗮𝗽𝗽
                   |                                |                        |
T+5000ms           ├─ instantiateActivity("ShareActivity")                  |
                   |  ├─ endsWith(".MainActivity") = FALSE                   |
                   |  └─ SKIPPED - No timestamp set |                        |
                   |                                |                        |
T+5050ms           ├─ ShareActivity.onCreate()      |                        🖥️ Share sheet
                   |                                |                           visible
                   |                                |                        |
                   |   ... user interacts with share screen ...               |
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝘀𝗲𝗿 𝗰𝗼𝗺𝗽𝗹𝗲𝘁𝗲𝘀 𝘀𝗵𝗮𝗿𝗲,
                   |                                |                           𝗻𝗮𝘃𝗶𝗴𝗮𝘁𝗲𝘀 𝘁𝗼 𝗺𝗮𝗶𝗻 𝗮𝗽𝗽
                   |                                |                        |
T+10000ms 🟢        ├─ instantiateActivity()         |                        |
         ┃          |  ├─ endsWith(".MainActivity") = TRUE                    |
         ┃          |  ├─ markActivityInstantiation()                         |
         ┃          |  ├─ 𝘄𝗮𝘀𝗜𝗻𝗶𝘁𝗶𝗮𝗹𝗶𝘇𝗲𝗖𝗼𝗺𝗽𝗹𝗲𝘁𝗲=𝘁𝗿𝘂𝗲 (init done during share) |
         ┃          |  ├─ staticMainActivityCreationTime == null = TRUE       |
         ┃          |  └─ 𝘀𝘁𝗮𝘁𝗶𝗰𝗠𝗮𝗶𝗻𝗔𝗰𝘁𝗶𝘃𝗶𝘁𝘆=𝟭𝟬𝟬𝟬𝟬                        |
         ┃          |                                |                        |
T+10000ms┃          |                                ●                        🖥️ Splash screen
         ┃          |                                |                           visible
         ┃          |                                |                        |
T+10050ms┃          |                            onCreate()                   |
         ┃          |                                |                        |
         ┃          |                                ├─ Inflate views         |
         ┃          |                                ├─ Init React            |
         ┃          |                                |                        |
T+13500ms┃          |                                ├─ Content renders       |
         ┃          |                                |                        |
         ┃          |                                |                        |
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 10000ms                                              ║
          ║ TTI = [view visible time] - 10000ms                                   ║
          ╚══════════════════════════════════════════════════════════════════════╝

9g: Long-press → Incoming Call - RARE

9g (COLD): Long-press → Incoming Call (COLD)

Description: User long-presses → Process starts → Incoming call arrives → MainActivity opens first → User answers call.

Result: COLD START

Visual Diagram:

                 Process (NEW)                   MainActivity                User
                    |                                |                        |
                    |                                |                        👆 𝗨𝘀𝗲𝗿 𝗹𝗼𝗻𝗴 𝗽𝗿𝗲𝘀𝘀𝗲𝘀 𝗶𝗰𝗼𝗻
                    |                                |                           (Samsung: process starts here;
                    |                                |                            Pixel: process doesn't start)
                    |                                |                        |
T+0ms              ┿━━━ Process starts              |                        |
                   |     (Samsung only; Pixel:       |                        |
                   |      starts later when          |                        |
                   |      call arrives)              |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |                                |                        |
                   |                                |                        📞 𝗜𝗻𝗰𝗼𝗺𝗶𝗻𝗴 𝗰𝗮𝗹𝗹
                   |                                |                        |
T+120ms  🟢         ├─ instantiateActivity()         |                        |
         ┃          |  ├─ endsWith(".MainActivity") = TRUE                    |
         ┃          |  ├─ markActivityInstantiation()                         |
         ┃          |  ├─ 𝘄𝗮𝘀𝗜𝗻𝗶𝘁𝗶𝗮𝗹𝗶𝘇𝗲𝗖𝗼𝗺𝗽𝗹𝗲𝘁𝗲=𝗳𝗮𝗹𝘀𝗲 (init still running!)  |
         ┃          |  ├─ staticMainActivityCreationTime == null = TRUE       |
         ┃          |  └─ 𝘀𝘁𝗮𝘁𝗶𝗰𝗠𝗮𝗶𝗻𝗔𝗰𝘁𝗶𝘃𝗶𝘁𝘆=𝟭𝟮𝟬                        |
         ┃          |                                |                        |
T+120ms  ┃          |                                ●                        🖥️ Splash screen
         ┃          |                                |                           visible
         ┃          |                                |                        |
T+160ms  ┃          |                            onCreate()                   |
         ┃          |                                |                        |
         ┃          |                                ├─ Inflate views         |
         ┃          |                                ├─ Init React            |
                   |                                |                        |
                   |   (IncomingCallActivity may    |                        |
                   |    appear as overlay/system    |                        |
                   |    UI, not tracked in TTI)     |                        |
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝗮𝗻𝘀𝘄𝗲𝗿𝘀 𝗰𝗮𝗹𝗹
                   |                                |                        |
T+3500ms ┃          └─ initialize() complete         |                        |
         ┃                                           ├─ Content renders       |
         ┃                                           |                        |
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appOpenedTime = 120ms                                                ║
          ║ appStartType = COLD (initialize not complete when MA opened)          ║
          ║ TTI = [view visible time] - 120ms                                    ║
          ╚══════════════════════════════════════════════════════════════════════╝

9h (WARM): Long-press → Incoming Call (WARM)

Description: User long-presses → Process starts → User waits → Incoming call arrives after initialize() completes → MainActivity opens first.

Result: WARM START

Visual Diagram:

                 Process (NEW)                   MainActivity                User
                    |                                |                        |
                    |                                |                        👆 𝗨𝘀𝗲𝗿 𝗹𝗼𝗻𝗴 𝗽𝗿𝗲𝘀𝘀𝗲𝘀 𝗶𝗰𝗼𝗻
                    |                                |                           (Samsung: process starts here;
                    |                                |                            Pixel: process doesn't start)
                    |                                |                        |
T+0ms              ┿━━━ Process starts              |                        |
                   |     (Samsung only; Pixel:       |                        |
                   |      starts later when          |                        |
                   |      call arrives)              |                        |
                   |                                |                        |
T+50ms             ├─ initialize() (bg thread)      |                        |
                   |                                |                        |
T+100ms            |                                |                        🖥️ Shortcuts menu appears
                   |                                |                        |
                   |                                |                        ⏱️ 𝗨𝘀𝗲𝗿 𝘄𝗮𝗶𝘁𝘀
                   |                                |                        |
T+3500ms           └─ initialize() complete         |                        |
                   |                                |                        |
                   |                                |                        📞 𝗜𝗻𝗰𝗼𝗺𝗶𝗻𝗴 𝗰𝗮𝗹𝗹
                   |                                |                        |
T+4000ms  🟢         ├─ instantiateActivity()         |                        |
         ┃          |  ├─ endsWith(".MainActivity") = TRUE                    |
         ┃          |  ├─ markActivityInstantiation()                         |
         ┃          |  ├─ 𝘄𝗮𝘀𝗜𝗻𝗶𝘁𝗶𝗮𝗹𝗶𝘇𝗲𝗖𝗼𝗺𝗽𝗹𝗲𝘁𝗲=𝘁𝗿𝘂𝗲 (init finished)          |
         ┃          |  ├─ staticMainActivityCreationTime == null = TRUE       |
         ┃          |  └─ 𝘀𝘁𝗮𝘁𝗶𝗰𝗠𝗮𝗶𝗻𝗔𝗰𝘁𝗶𝘃𝗶𝘁𝘆=𝟰𝟬𝟬𝟬                        |
         ┃          |                                |                        |
T+4000ms ┃          |                                ●                        🖥️ Splash screen
         ┃          |                                |                           visible
         ┃          |                                |                        |
T+4050ms┃          |                            onCreate()                   |
         ┃          |                                |                        |
         ┃          |                                ├─ Inflate views         |
         ┃          |                                ├─ Init React            |
                   |                                |                        |
                   |   (IncomingCallActivity may    |                        |
                   |    appear as overlay/system    |                        |
                   |    UI, not tracked in TTI)     |                        |
                   |                                |                        |
                   |                                |                        👆 𝗨𝘀𝗲𝗿 𝗮𝗻𝘀𝘄𝗲𝗿𝘀 𝗰𝗮𝗹𝗹
                   |                                |                        |
T+4100ms┃          |                                ├─ Content renders       |
         ┃          |                                |                        |
         ┃          |                                |                        |
         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔴 [𝗧𝗧𝗜 𝗘𝗡𝗗]

          ╔══════════════════════════════════════════════════════════════════════╗
          ║ TTI MEASUREMENT RANGE (🟢 start → 🔴 end)                            ║
          ║ appStartType = WARM (initialize complete when MainActivity opened)   ║
          ║ appOpenedTime = 4000ms                                               ║
          ║ TTI = [view visible time] - 4000ms                                    ║
          ║ IncomingCallActivity (overlay/system UI) correctly EXCLUDED           ║
          ╚══════════════════════════════════════════════════════════════════════╝

Legend:
  🟢    = TTI measurement START (appOpenedTime)
  🔴    = TTI measurement END (calculated in JS)
  👆    = User tap/interaction (triggers action)
  🖥️    = User sees UI (splash, main app, etc.)
  📱    = System event (notification arrives)
  📞    = Incoming call
  🔄    = Configuration change (rotation)
  ●     = MainActivity instance
  ┃     = TTI measurement in progress
  𝗕𝗼𝗹𝗱  = Values used in TTI calculation for this scenario
  Normal = Events that occur (helpful context)

API < 28 Support:

  • Manual tracking in onStartCommand() and onReceive() with API version checks
  • Fallback in MainActivity.onCreate() for timestamp capture
  • All scenarios work identically on older Android versions

IOS TTI Launch Scenarios

Implementation Note

appOpenedTime always uses AppOpenedTimestamp: iOS writes AppOpenedTimestamp in AppDelegate via markAppOpened(), and that timestamp is the start anchor used by trackAppUIViewed() / trackAppUIViewed2(). appStartType is classified on the first handleDidBecomeActive(_:) after DCDTTIAppStartLifecycleTracker.init(), then later read from JS via AppStartInfo.getAppStartInfo() -> NativeModules.TTIManager.getAppStartType(). The initial classification is preserved and later warm foreground transitions must not overwrite it.


Scenarios Analysis

Scenario 1: Normal App Open (COLD START)

Description: User taps app icon -> Fresh process starts -> App becomes active immediately

Result: COLD START

Visual Diagram:

                 Process                        Lifecycle / Tracker              User
                    |                                   |                        |
                    |                                   |                        👆 User taps app icon
                    |                                   |                        |
T+0ms              +--- Process starts                 |                        |
                   |                                   |                        |
T+10ms             +- AppDelegate.didFinishLaunching   |                        |
                   |   \- DCDTTIAppStartLifecycleTracker.init()     |                        |
                   |                                   +- addObserver(didBecomeActive/willEnterForeground/didEnterBackground)
                   |                                   +- didFinishLaunchingTimestamp = Date()
                   |                                   |                        |
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened()                  |                        |
          |        |   \- AppOpenedTimestamp set       |                        |
          |        |                                   +- AppOpenedTimestamp written
          |        |                                   |                        |
T+120ms     |       +- UIApplication.didBecomeActive   |                        |
          |        |                                   +- handleDidBecomeActive(_:)
          |        |                                   +- timeSinceLaunch < 1.5
          |        |                                   +- initialAppStartType = cold
          |        |                                   \- lifecycleState = .active
          |        |                                   |                        |
T+3500ms   |       |                                   |                        🖥️ Content visible
          |        |                                   |                        |
T+3600ms   |       |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> "cold"
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]

          +----------------------------------------------------------------------+
          | TTI MEASUREMENT RANGE (🟢 start -> 🔴 end)                            |
          | appOpenedTime = AppOpenedTimestamp                                   |
          | JS reads start type in AppStartInfo.getAppStartInfo() before         |
          | APP_UI_VIEWED2 is tracked                                            |
          +----------------------------------------------------------------------+


Scenario 2: Cold Launch From Deep Link / URL Scheme

Description: User taps a deep link / URL scheme -> Fresh process starts -> App becomes active immediately

Result: COLD START

Visual Diagram:

                 Process                        Lifecycle / Tracker              User
                    |                                   |                        |
                    |                                   |                        👆 User taps deep link
                    |                                   |                        |
T+0ms              +--- Process starts                 |                        |
T+10ms             +- didFinishLaunching               +- DCDTTIAppStartLifecycleTracker.init()
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
T+120ms     |       +- didBecomeActive                 +- handleDidBecomeActive(_:)
          |        |                                   \- initialAppStartType = cold
          |        |                                   |                        |
T+3500ms   |       |                                   |                        🖥️ Content visible
          |        |                                   |                        |
T+3600ms   |       |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> "cold"
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]

          +----------------------------------------------------------------------+
          | Launch source should not matter if first activation happens quickly  |
          | JS should later read "cold" before APP_UI_VIEWED2 is tracked         |
          +----------------------------------------------------------------------+


Scenario 3: Cold Launch From Notification Tap

Description: App is dead -> User taps a visible notification -> App launches directly to foreground

Result: COLD START

Visual Diagram:

                 Process                        Lifecycle / Tracker              User
                    |                                   |                        |
                    |                                   |                        📱 Notification arrives
                    |                                   |                        |
                    |                                   |                        👆 User taps notif
                    |                                   |                        |
T+0ms              +--- Process starts                 |                        |
T+10ms             +- didFinishLaunching               +- DCDTTIAppStartLifecycleTracker.init()
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
T+120ms     |       +- didBecomeActive                 +- handleDidBecomeActive(_:)
          |        |                                   +- timeSinceLaunch < 1.5
          |        |                                   \- initialAppStartType = cold
          |        |                                   |                        |
T+3500ms   |       |                                   |                        🖥️ Content visible
          |        |                                   |                        |
T+3600ms   |       |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> "cold"
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]

          +----------------------------------------------------------------------+
          | Notification tap from a dead process should classify as COLD         |
          | unless the process was already started in background before UI       |
          +----------------------------------------------------------------------+


Scenario 4: Prewarm Launch

Description: The OS prewarms the process (ActivePrewarm == 1) before the app becomes visible

Result: PREWARM START

Visual Diagram:

                 Process                        Lifecycle / Tracker              User
                    |                                   |                        |
T+0ms              +--- Process prewarmed              |                        |
T+10ms             +- didFinishLaunching               +- DCDTTIAppStartLifecycleTracker.init()
                   |                                   +- addObserver(didBecomeActive/willEnterForeground/didEnterBackground)
                   |                                   +- isActivePrewarm = true
                   |                                   |                        |
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
T+5000ms    |       +- didBecomeActive                 +- handleDidBecomeActive(_:)
          |        |                                   \- initialAppStartType = prewarm
          |        |                                   |                        |
T+8500ms   |       |                                   |                        🖥️ Content visible
          |        |                                   |                        |
T+8600ms   |       |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> "prewarm"
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]

          +----------------------------------------------------------------------+
          | Initial classification must be PREWARM regardless of timeSinceLaunch |
          | JS should later read "prewarm" before APP_UI_VIEWED2 is tracked      |
          +----------------------------------------------------------------------+


Scenario 5: Background Wake -> User Opens Later (WARM START)

Description: Process starts in background first (e.g. background push / background wake) -> User opens app later

Result: WARM START

Note: you must use xcrun devicectl device process terminate (SIGTERM), to terminate the app and not swipe-up to force close it. Swipe-up sets iOS's force-quit flag and the app will never receive background wakes until manually opened again.

Visual Diagram:

                 Process                        Lifecycle / Tracker              User
                    |                                   |                        |
T+0ms              +--- Process starts in background   |                        |
T+10ms             +- didFinishLaunching               +- DCDTTIAppStartLifecycleTracker.init()
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
[Process remains backgrounded long enough for heuristic]
          |        |                                   |                        |
T+5000ms    |       +- didBecomeActive                 +- handleDidBecomeActive(_:)
          |        |                                   +- timeSinceLaunch >= 1.5
          |        |                                   \- initialAppStartType = warm
          |        |                                   |                        👆 User opens app
          |        |                                   |                        |
T+8500ms   |       |                                   |                        🖥️ Content visible
          |        |                                   |                        |
T+8600ms   |       |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> "warm"
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]

          +----------------------------------------------------------------------+
          | This is the key heuristic WARM path                                  |
          | JS should later read "warm" before APP_UI_VIEWED2 is tracked         |
          +----------------------------------------------------------------------+


Scenario 6: Background Wake But User Opens Too Quickly

Description: Process starts in background first -> User opens app before 1.5 seconds elapse

Result: COLD START ✅ (current heuristic)

Visual Diagram:

                 Process                        Lifecycle / Tracker              User
                    |                                   |                        |
T+0ms              +--- Process starts in background   |                        |
T+10ms             +- didFinishLaunching               +- DCDTTIAppStartLifecycleTracker.init()
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
T+500ms     |       +- didBecomeActive                 +- handleDidBecomeActive(_:)
          |        |                                   +- timeSinceLaunch < 1.5
          |        |                                   \- initialAppStartType = cold
          |        |                                   |                        👆 User opens app
          |        |                                   |                        |
T+3500ms   |       |                                   |                        🖥️ Content visible
          |        |                                   |                        |
T+3600ms   |       |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> "cold"
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]

          +----------------------------------------------------------------------+
          | This is an expected heuristic limitation                             |
          | Fast background-start-then-open still classifies as COLD             |
          +----------------------------------------------------------------------+


Scenario 7: Cold Launch -> Background -> Foreground

Description: App launches cold -> User backgrounds app -> User foregrounds app before telemetry reads start type

Result: COLD START PRESERVED

Visual Diagram:

                 Process                        Lifecycle / Tracker              User
                    |                                   |                        |
T+0ms              +--- Process starts                 |                        |
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
T+120ms     |       +- didBecomeActive                 +- handleDidBecomeActive(_:) -> initialAppStartType = cold
          |        |                                   |                        |
[User backgrounds app]
          |        |                                   +- handleDidEnterBackground(_:)
          |        |                                   \- lifecycleState = .backgrounded
          |        |                                   |                        |
[User foregrounds app]
          |        |                                   +- handleWillEnterForeground(_:) -> lifecycleState = .awaitingWarmActivation
          |        |                                   +- handleDidBecomeActive(_:)
          |        |                                   \- initialAppStartType preserved as cold
          |        |                                   |                        |
[JS later reads]
          |        |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> "cold"
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]

          +----------------------------------------------------------------------+
          | Later warm foreground cycles must not overwrite the initial COLD     |
          | classification before telemetry reads it                             |
          +----------------------------------------------------------------------+


Scenario 8: Warm Launch -> Background -> Foreground

Description: Initial launch classifies warm -> User backgrounds and foregrounds app again

Result: WARM START PRESERVED

Visual Diagram:

                 Process                        Lifecycle / Tracker              User
                    |                                   |                        |
T+0ms              +--- Background wake                |                        |
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
T+5000ms    |       +- didBecomeActive                 +- handleDidBecomeActive(_:) -> initialAppStartType = warm
          |        |                                   |                        |
[User backgrounds and foregrounds]
          |        |                                   +- handleDidEnterBackground(_:) -> lifecycleState = .backgrounded
          |        |                                   +- handleWillEnterForeground(_:) -> lifecycleState = .awaitingWarmActivation
          |        |                                   \- handleDidBecomeActive(_:) -> initialAppStartType preserved as warm
          |        |                                   |                        |
[JS later reads]
          |        |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> "warm"
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]

          +----------------------------------------------------------------------+
          | Initial WARM classification must remain stable across later opens    |
          +----------------------------------------------------------------------+


Scenario 9: Prewarm Launch -> Background -> Foreground

Description: Initial launch classifies prewarm -> User backgrounds and foregrounds app again

Result: PREWARM START PRESERVED

Visual Diagram:

                 Process                        Lifecycle / Tracker              User
                    |                                   |                        |
T+0ms              +--- Process prewarmed              |                        |
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
T+5000ms    |       +- didBecomeActive                 +- handleDidBecomeActive(_:) -> initialAppStartType = prewarm
          |        |                                   |                        |
[User backgrounds and foregrounds]
          |        |                                   +- handleDidEnterBackground(_:) -> lifecycleState = .backgrounded
          |        |                                   +- handleWillEnterForeground(_:) -> lifecycleState = .awaitingWarmActivation
          |        |                                   \- handleDidBecomeActive(_:) -> initialAppStartType preserved as prewarm
          |        |                                   |                        |
[JS later reads]
          |        |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> "prewarm"
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]

          +----------------------------------------------------------------------+
          | Initial PREWARM classification must not be replaced by later WARM    |
          +----------------------------------------------------------------------+


Scenario 10: Multiple Background / Foreground Cycles Before JS Read

Description: trackAppUIViewed2 is delayed -> User backgrounds and foregrounds app multiple times before getAppStartType() runs

Result: INITIAL START TYPE PRESERVED

Visual Diagram:

                 Process                        Lifecycle / Tracker              User
                    |                                   |                        |
T+0ms              +--- Process starts                 |                        |
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
T+120ms     |       +- didBecomeActive                 +- handleDidBecomeActive(_:) -> initialAppStartType = cold/warm/prewarm
          |        |                                   |                        |
[Many later cycles]
          |        |                                   +- handleDidEnterBackground(_:)
          |        |                                   +- handleWillEnterForeground(_:)
          |        |                                   +- handleDidBecomeActive(_:) -> initialAppStartType preserved
          |        |                                   +- handleDidEnterBackground(_:)
          |        |                                   +- handleWillEnterForeground(_:)
          |        |                                   \- handleDidBecomeActive(_:) -> initialAppStartType preserved
          |        |                                   |                        |
[JS finally reads]
          |        |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> <same initial type>
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          |        |                                   |                        |
          --------------------------------------------------------------- 🔴 [TTI END]

          +----------------------------------------------------------------------+
          | The resolved type must always equal the first app start classification|
          +----------------------------------------------------------------------+


Scenario 11: requiresMainQueueSetup Changes Whether constantsToExport() Runs Before Or After Activation

Description: React Native initializes TTIManager at different times relative to activation depending on whether requiresMainQueueSetup is true or false

Result: NO IMPACT ON APP START TYPE

Visual Diagram:

Case A: requiresMainQueueSetup = true

                 Process                        Lifecycle / Tracker              JS / RN
                    |                                   |                        |
T+0ms              +--- Process starts                 |                        |
T+10ms             +- didFinishLaunching               +- DCDTTIAppStartLifecycleTracker.init()
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
          |        |                                   |                        +- eager module init on main
          |        |                                   \- constantsToExport()
          |        |                                   |                        |
T+120ms     |       +- didBecomeActive                 +- handleDidBecomeActive(_:) -> initialAppStartType = ...
          |        |                                   |                        |
[JS later reads]
          |        |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> ...
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]
Case B: requiresMainQueueSetup = false

                 Process                        Lifecycle / Tracker              JS / RN
                    |                                   |                        |
T+0ms              +--- Process starts                 |                        |
T+10ms             +- didFinishLaunching               +- DCDTTIAppStartLifecycleTracker.init()
T+20ms      🟢      +- tracker.markAppOpened() -> DCDTTIManager.markAppOpened() +- AppOpenedTimestamp written
          |        |                                   |                        |
T+120ms     |       +- didBecomeActive                 +- handleDidBecomeActive(_:) -> initialAppStartType = ...
          |        |                                   |                        |
          |        |                                   |                        +- lazy module init later
          |        |                                   \- constantsToExport()
          |        |                                   |                        |
[JS later reads]
          |        |                                   |                        JS calls trackAppUIViewed2()
          |        |                                   |                        \- AppStartInfo.getAppStartInfo()
          |        |                                   \- getAppStartType() -> ...
          |        |                                   |                        \- APP_UI_VIEWED2 tracked
          --------------------------------------------------------------- 🔴 [TTI END]
          +----------------------------------------------------------------------+
          | requiresMainQueueSetup=true  -> constantsToExport before activation  |
          | requiresMainQueueSetup=false -> constantsToExport after activation   |
          | AppStartType no longer depends on either ordering                    |
          +----------------------------------------------------------------------+
@hannojg
Copy link
Copy Markdown

hannojg commented Apr 7, 2026

ios part lgtm

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