Android foreground service and deep links implementing background calls and direct navigation

Key takeaways

Android 14+ hard-enforces foreground service types. Every startForeground() call needs a declared foregroundServiceType and the matching runtime permission — or your app crashes with SecurityException.

Firebase Dynamic Links is gone. FDL shut down on 25 August 2025. If your deep-link stack still imports firebase-dynamic-links, every shared invite is already returning a 404 — migrate to verified Android App Links plus Branch, AppsFlyer OneLink, Adjust, or Singular for deferred deep linking.

VoIP calls need phoneCall + CallStyle + Telecom. The right combination is a foreground service of type phoneCall|microphone|camera, a CallStyle notification (Android 12+), and the Jetpack Core-Telecom integration to survive Doze, Do-Not-Disturb, and task-manager sweeps.

Pick the right background primitive. Foreground service for user-visible long-running work (calls, live uploads, navigation). shortService for < 3-minute bursts. WorkManager for retryable batch jobs. Do not use FGS as a general background worker — Play Store policy rejects it.

Fora Soft has shipped this on production video apps. We run phoneCall foreground services and verified App Links on Android 14+ projects like ProVideoMeeting, Second Phone, and BrainCert.

Why Fora Soft wrote this playbook

We have been building Android apps for video calling, telemedicine, and e‑learning since 2005. Every customer-facing app we ship needs two things that became much more complicated in 2024–2025: a foreground service that keeps a call alive after the user locks the screen, and a deep-link scheme that survives both a cold install and Google’s deprecation of Firebase Dynamic Links.

Android 14 tightened the foreground service rules so hard that a one-line manifest typo now ships as a crash to production. Android 15 added notification cooldown, service timeouts, and further restrictions. Firebase Dynamic Links sunset on 25 August 2025, so every app still relying on FDL links is serving broken invite URLs. This playbook is the field guide we hand new Android engineers on our video-conferencing and custom software teams.

Read it top-to-bottom and you’ll know how to ship foreground services and deep links on Android 14–15 without breaking Play Store review, without crashing on cold start, and without writing a second scheme attribute that no modern Android respects.

Shipping an Android 14 call app and seeing ForegroundServiceStartNotAllowedException?

Fora Soft has migrated foreground services and deep links across dozens of video-calling and telehealth apps. Paste your manifest — we’ll tell you which type/permission pair is missing in 30 minutes.

Book a 30-min call → WhatsApp → Email us →

What changed in 2024–2025 — the 90-second briefing

Three platform shifts rewrote the playbook. Skim this section before touching code.

1. Android 14 (API 34) foreground service types. Any app targeting API 34 must declare a foregroundServiceType attribute on every <service> and call startForeground(notif, type) with the same type mask. Missing or mismatched type = MissingForegroundServiceTypeException; missing the runtime permission for the type = SecurityException.

2. Android 15 (API 35) tightens the screws. dataSync and mediaProcessing services now have a cumulative 6-hour daily cap; the BOOT_COMPLETED receiver can’t start several FGS types. A new notification cooldown mutes repeated alerts. Most VoIP/video apps are unaffected because phoneCall is explicitly exempt from the cooldown.

3. Firebase Dynamic Links is dead. Google announced the deprecation in August 2023 and fully shut the service down on 25 August 2025. Existing short-links now 404; FirebaseDynamicLinks.getInstance() returns null or throws. The replacement stack is verified Android App Links for your own HTTPS domain, plus a third-party attribution provider (Branch, AppsFlyer OneLink, Adjust, Kochava, Singular) if you need deferred deep linking, install attribution, or short URLs.

Those three shifts reshape the rest of the article. The foreground service section below assumes API 34+; the deep-link section assumes you have removed every line of firebase-dynamic-links.

Why a foreground service is still the right tool for a call

Android aggressively kills background work to save battery. Doze, App Standby, cached-app culling, and per-vendor task killers (especially on Xiaomi, Vivo, OnePlus) will reap any plain Service subclass within minutes — mid-call. A foreground service says to the OS “this work is user-visible and must not be killed,” and it enforces that contract by forcing you to show a persistent notification.

For WebRTC and VoIP apps, the FGS is the container that keeps the PeerConnection, audio routing, and call state alive while the user switches apps, answers a knock at the door, or locks the screen. Without it, 30% of calls drop the moment the app backgrounds on an aggressive OEM.

The 11 foreground service types — which permission each needs

Android 14 enforces this table at runtime. If the type you declare doesn’t match the permission you hold, startForeground() throws. Keep this handy as a manifest review checklist.

Type Required runtime permission Typical use
phoneCall FOREGROUND_SERVICE_PHONE_CALL + ManageOwnCalls / Telecom role VoIP calls, conferences, WebRTC
microphone FOREGROUND_SERVICE_MICROPHONE + RECORD_AUDIO Voice recording, audio messaging
camera FOREGROUND_SERVICE_CAMERA + CAMERA Video call, live streaming
mediaPlayback FOREGROUND_SERVICE_MEDIA_PLAYBACK Music, podcast, background video
mediaProjection FOREGROUND_SERVICE_MEDIA_PROJECTION + MediaProjection consent Screen sharing, recording
location FOREGROUND_SERVICE_LOCATION + foreground location permission Navigation, ride-hailing, fitness tracking
connectedDevice FOREGROUND_SERVICE_CONNECTED_DEVICE + BT / UWB / NFC Wearables, car kits, peripherals
dataSync FOREGROUND_SERVICE_DATA_SYNC Uploads/downloads, backup (capped in Android 15)
remoteMessaging FOREGROUND_SERVICE_REMOTE_MESSAGING Chat sync across devices
shortService None (auto-stops after ~3 min) Short flushes, cache clears, outbox drain
specialUse FOREGROUND_SERVICE_SPECIAL_USE + Play Console declaration Anything that fits none of the above (rare; requires Play review)

Pattern A — a foreground service for a VoIP/video call, end to end

The canonical call service needs three types OR’d together: phoneCall|microphone|camera. You also need the three matching runtime permissions, plus POST_NOTIFICATIONS on Android 13+. Below is the minimal pattern we ship.

Step 1. Manifest declarations

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />

<service
    android:name=".call.OngoingCallService"
    android:enabled="true"
    android:exported="false"
    android:foregroundServiceType="phoneCall|microphone|camera" />

Step 2. The service class (Kotlin, Hilt-injected manager)

@AndroidEntryPoint
class OngoingCallService : Service() {

    @Inject lateinit var callManager: AbstractCallManager

    override fun onBind(intent: Intent): IBinder? = null

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = CallNotificationBuilder(this).buildCallStyle(callManager.currentCall)
        ServiceCompat.startForeground(
            this,
            ONGOING_NOTIFICATION_ID,
            notification,
            ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL or
                ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE or
                ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA,
        )
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        callManager.teardown()
    }
}

Two non-obvious details save you pain later. First, use ServiceCompat.startForeground(...) instead of the raw startForeground overload — the compat helper applies the Android 14 type argument on every API level. Second, START_STICKY is usually what you want for a call; the system will restart the service if it gets killed, and your CallManager should be idempotent about re-joining.

Step 3. Start and stop reactively

// Bind service start to the call state flow.
callManager.isInCall
    .distinctUntilChanged()
    .collect { inCall ->
        val intent = Intent(context, OngoingCallService::class.java)
        if (inCall) {
            ContextCompat.startForegroundService(context, intent)
        } else {
            context.stopService(intent)
        }
    }

The 5-second race to startForeground() after startForegroundService() trips every team once — if you miss it, Android kills the service and throws ForegroundServiceDidNotStartInTimeException. Build the notification synchronously, before any network I/O.

CallStyle notifications — the right UI for a call FGS

Starting with Android 12, Notification.CallStyle pins call notifications to the top of the shade, uses the system call UI, and plays nicely with Do Not Disturb. CallStyle is the only way to publish a call notification that a Pixel or Samsung phone won’t throttle or collapse.

val person = Person.Builder().setName(callerName).setIcon(callerAvatar).build()
val hangUpIntent = PendingIntent.getService(
    this, 0,
    Intent(this, OngoingCallService::class.java).setAction(ACTION_HANG_UP),
    PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
)

val notification = NotificationCompat.Builder(this, CHANNEL_ID_CALL)
    .setSmallIcon(R.drawable.ic_call)
    .setContentIntent(fullScreenIntent)
    .setOngoing(true)
    .setCategory(NotificationCompat.CATEGORY_CALL)
    .setStyle(NotificationCompat.CallStyle.forOngoingCall(person, hangUpIntent))
    .build()

Pair CallStyle with Jetpack Core-Telecom (androidx.core:core-telecom) and you get automatic audio routing to Bluetooth headsets, compatibility with phone-app do-not-disturb, and the system’s own incoming-call UI on Android 14+.

Reach for CallStyle when: your app places VoIP/video calls and needs to coexist with the system phone UI. Skip it for pure media playback, uploads, or navigation — those belong in MediaStyle or default notifications.

shortService vs foreground service vs WorkManager

Three primitives, three mandates. Using the wrong one gets your app throttled, killed, or rejected by Play.

1. Foreground service (FGS). Long-running, user-visible work. Calls, ongoing uploads with a progress UI, turn-by-turn navigation. Declared foregroundServiceType + permission + persistent notification. No time cap on phoneCall, mediaPlayback; 6-hour daily cap on dataSync in Android 15.

2. shortService. A bounded < 3-minute burst. No permission needed, but strict timeout. Perfect for flushing an outbox, finishing a handshake, or migrating a database. The system kills it if you exceed the window.

3. WorkManager. Retryable, deferrable batch work. Periodic photo upload, nightly sync, background compression. Under the hood WorkManager can escalate to a foreground worker via setForeground() with a ForegroundInfo that still requires type + permission. Otherwise it uses JobScheduler, which is Doze-friendly.

Rule of thumb: if the user is going to see it happening, use FGS or WorkManager-foreground. If the work is a background obligation that can wait until the device charges and has Wi-Fi, use plain WorkManager.

The four crashes every team hits the first time on Android 14

1. MissingForegroundServiceTypeException. You forgot android:foregroundServiceType on the <service>, or the type argument in the startForeground() call. Fix: add both.

2. SecurityException: Starting FGS of type phoneCall requires FOREGROUND_SERVICE_PHONE_CALL. The manifest declares the type but the <uses-permission> is missing. Fix: add the exact permission from the table above.

3. ForegroundServiceStartNotAllowedException. You called startForegroundService from the background (e.g. a BroadcastReceiver) without an approved exemption. Fix: trigger the service from a high-priority FCM message, a full-screen intent, or after a user action. For incoming calls, use the Telecom framework.

4. ForegroundServiceDidNotStartInTimeException. Your service took longer than 5 seconds to reach startForeground(). Fix: build the notification first, then do network calls; never block onStartCommand on I/O.

Need a second pair of eyes on your Android 14 upgrade plan?

We have migrated WebRTC call apps and e‑learning clients to API 34/35 with Agent Engineering-accelerated workflows. Send us your manifest and crash logs — we’ll respond with a concrete plan.

Book a 30-min audit call → WhatsApp → Email us →

Three terms get thrown around interchangeably. Only two still matter.

1. Custom-scheme deep links (myapp://meeting/123). Simple, old, still supported. Drawbacks: no cross-app guarantees, users see the disambiguation dialog if two apps claim the scheme, and iOS handles them differently. Use for private internal flows and adb testing — not for marketing links.

2. Verified Android App Links (https://yourdomain.com/meeting/123). HTTPS URLs you own, auto-verified via /.well-known/assetlinks.json on your domain. Android 12+ will open your app without the disambiguation dialog. This is the canonical modern answer for user-shareable deep links.

3. Firebase Dynamic Links — DEPRECATED. Shut down on 25 August 2025. Do not build new features on FDL; audit existing apps and remove the SDK before your next release.

1. Add an intent filter with autoVerify="true". In the Activity that handles deep links:

<activity android:name=".ui.MainActivity" android:exported="true">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" android:host="links.example.com" />
        <data android:pathPattern="/meeting/.*" />
    </intent-filter>
</activity>

2. Publish assetlinks.json on your domain. Must be served from https://links.example.com/.well-known/assetlinks.json with Content-Type: application/json and no redirects.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.calls",
    "sha256_cert_fingerprints": [
      "14:6D:E9:83:...:5B:4C:29"
    ]
  }
}]

Include the Play App Signing SHA-256 fingerprint from the Play Console, not your upload key. Missing or wrong fingerprint is the #1 reason App Links silently fall back to the disambiguation dialog.

3. Handle the intent in the Activity.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handleDeepLink(intent)
}

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    handleDeepLink(intent)
}

private fun handleDeepLink(intent: Intent) {
    val data = intent.data ?: return
    when (data.pathSegments.firstOrNull()) {
        "meeting" -> {
            val id = data.pathSegments.getOrNull(1) ?: return
            callManager.joinMeeting(id)
        }
    }
}

4. Verify with adb.

adb shell pm get-app-links com.example.calls
adb shell am start -a android.intent.action.VIEW \
    -d "https://links.example.com/meeting/abc" com.example.calls

5. Add a server-side fallback. Point links.example.com/meeting/* at a tiny web page that detects Android vs iOS, renders the meeting join screen, and redirects to the Play Store if the app is not installed. This replaces the “take me to Play then open the app” behaviour Firebase Dynamic Links used to provide.

“Deferred” deep links carry context across a Play Store install. Click a meeting invite on a fresh device → install the app from Play → first launch drops you straight into the meeting. Verified App Links can’t do this alone because the install interrupts the intent. You need a third-party SDK to store the context and hand it back on first launch. Four production-ready options:

Provider Strengths Starting price Best for
Branch Mature deferred deep linking, rich link analytics, Google-recommended FDL replacement Free tier up to 10k MAU Most consumer apps migrating from FDL
AppsFlyer OneLink Strong attribution, marketing suite, enterprise contracts Custom quote Marketing-heavy apps with paid acquisition
Adjust Fraud prevention, deep-link customisation, strong in gaming Custom quote Mid-to-large apps with ad spend
Singular Unified attribution + deep linking, growing FDL migration share Custom quote Teams consolidating MMP tools
DIY backend Total control, zero vendor fee, you own the data Engineering time only Teams with a lean link volume and a web team

For most B2B communication apps we ship, Branch is the cleanest migration path: it accepts existing FDL-style links, keeps the deferred-install flow intact, and the free tier covers pilots. For growth-driven consumer apps that already run AppsFlyer or Adjust, their OneLink / deep-link products collapse nicely into the existing attribution stack.

DIY deferred deep links (when you don’t want a vendor)

If you run a small link volume and already own a web stack, you can ship deferred deep linking in a weekend:

  • Serve the shareable URL (https://links.example.com/meeting/abc) from your own web server.
  • On the landing page, store a hashed fingerprint (IP + user-agent + screen size + timestamp) against the context ID in a short-TTL Redis record.
  • Redirect the user to the Play Store if the App Link wasn’t installed; launch the app directly if it was.
  • On first launch after install, the app POSTs the same fingerprint to your backend. If it matches a pending record < 30 min old, return the meeting ID.
  • Idempotent join on receipt — the user lands in the meeting.

Match rates sit around 85–90%, which is lower than vendor SDKs (95%+) but acceptable for internal-facing or small-volume flows. For anything ad-driven, take the vendor.

Mini case — migrating a video-calling app to Android 14 + App Links

A client running a WebRTC video app with ~800k MAU came to us in mid-2025. They had two fires: Crashlytics was logging thousands of MissingForegroundServiceTypeException per day after a targetSdk bump to 34, and they still pointed half their onboarding at Firebase Dynamic Links with three months to go before the shutdown.

Our six-week plan: rewrite the call service with phoneCall|microphone|camera and ServiceCompat.startForeground, migrate to CallStyle notifications, pipe through Jetpack Core-Telecom, add verified App Links with an assetlinks.json on their marketing domain, and wire Branch for deferred deep linking on ad campaigns. Total scope: about 380 engineering hours, accelerated with our Agent Engineering workflow.

Outcome: FGS crash rate fell to zero within the first 14 days on API 34. Install-to-first-meeting conversion from shared invites rose 11% because App Links skipped the browser interstitial. Want a similar audit for your app? Book a 30-min architecture review.

A decision framework — pick the right pattern in five questions

1. Is the work user-visible and long-running? Yes → foreground service. No → WorkManager.

2. Is it a VoIP/video call? Yes → phoneCall|microphone|camera FGS + CallStyle notification + Jetpack Core-Telecom. No → pick the specific FGS type from the 11-row table.

3. Will users share a link that opens straight into the app? Yes → verified App Links on your domain with assetlinks.json. No → a custom scheme is enough for internal flows.

4. Do those links have to survive a Play Store install? Yes → Branch, AppsFlyer, Adjust, Singular, or a DIY fingerprint backend. No → App Links alone cover your need.

5. Do you depend on install attribution for paid ads? Yes → AppsFlyer, Adjust, or Singular covers both attribution and deferred linking. No → Branch or DIY is simpler and cheaper.

Five pitfalls we keep finding in audits

1. Shipping with firebase-dynamic-links still in the build. The SDK will not fail at compile time but every old link now returns 404. Remove the dependency, audit every invite flow, and replace with App Links + Branch/DIY.

2. Using the upload-key fingerprint in assetlinks.json. Play App Signing re-signs your APK with a different key; you must publish the Play App Signing SHA-256, not your local keystore. Check Play Console → Setup → App signing.

3. Calling startForegroundService from the background. Android 12+ throws ForegroundServiceStartNotAllowedException unless you have an approved exemption (FCM high-priority, alarm, Telecom ring). Route incoming calls through the Telecom framework or a full-screen-intent notification.

4. Using shortService for anything over two minutes. It looks free (no permission) but Android kills it at ~3 minutes. Use it only for flushes, handshakes, and migrations.

5. Ignoring cold-start intent handling. If the user taps a deep link when your Activity is not in memory, the intent arrives in onCreate. If it’s already in memory, it arrives in onNewIntent. Handle both or your link silently no-ops half the time.

KPIs: what to measure after the migration

Three buckets cover every dashboard we hand clients post-launch.

Quality KPIs. Crashlytics rate of ForegroundService*Exception per 1,000 sessions (target < 0.1), ANR rate during onStartCommand (target 0), and App-Link verification rate in Play Console Vitals (target > 95% of installs).

Business KPIs. Install-to-first-meeting conversion from shared invites (baseline before migration, uplift target > 10%), week-1 retention on users who arrived via a deep link, and call-completion rate on Android vs iOS.

Reliability KPIs. Median call duration before FGS was killed (target > 30 minutes), median time from backgrounding to call drop (target never), and Bluetooth audio reconnection success rate (target > 99%).

When not to use a foreground service at all

Reaching for FGS as a first instinct gets your app flagged by Google Play’s background-location and foreground-service policies. Three red flags:

1. Batch work without a user-visible reason. Photo backup, nightly index rebuild, log shipping. Use WorkManager with constraints; let Android pick a charging + Wi-Fi window.

2. “Keep-alive” for push messaging. Use FCM high-priority messages; don’t run a permanent FGS to poll your backend.

3. Location tracking after the app is closed. Android 10+ gates this behind background location permission; Play requires a declaration form. If the user sees no active session, you probably shouldn’t be tracking them.

Planning an Android 14/15 upgrade or FDL migration?

Fora Soft has shipped Android call apps since Gingerbread. We’ll audit your manifest, crash logs, and deep-link stack — and give you a fixed-scope plan.

Book a 30-min call → WhatsApp → Email us →

FAQ

Do I still need a foreground service on Android 14 if my WebRTC call already runs under android:launchMode="singleTop"?

Yes. Launch mode affects Activity reuse, not background execution. The foreground service is what tells the OS not to kill your process when the user switches apps or locks the screen. Without it, aggressive OEMs (Xiaomi, Vivo, OnePlus) will drop the call within a minute.

What exactly happens if Firebase Dynamic Links isn’t removed from my app?

As of 25 August 2025 the page.link domains return HTTP 410 and the SDK returns null / throws on getDynamicLink(). Old invite URLs in the wild are dead. Remove the SDK to avoid dragging its Crashlytics footprint, migrate to verified App Links on your own domain, and layer Branch/AppsFlyer for deferred linking.

Why does startForeground() crash with SecurityException on Android 14 even though my manifest is correct?

Check two things: (1) the exact FOREGROUND_SERVICE_* permission that matches your declared type must be listed as a <uses-permission>, and (2) you must pass the same type mask to ServiceCompat.startForeground(service, id, notification, type). Both the manifest and the Java/Kotlin call have to agree.

How do I test verified App Links before pushing to Play?

Publish assetlinks.json using the Play App Signing SHA-256, then run adb shell pm get-app-links your.package.name. You want to see “verified: true” for each host. On-device, open a deep-link URL from Gmail or Chrome: if no disambiguation dialog shows, App Links are working. Google’s Statement List Generator and Tester also validate the JSON against your domain.

What about iOS — does the same deep-link setup work?

The concept mirrors: iOS Universal Links are the App Link equivalent, powered by an apple-app-site-association file at /.well-known/apple-app-site-association. Your backend serves one file for Android (assetlinks.json) and one for iOS. All the FDL replacement SDKs (Branch, AppsFlyer, Adjust) handle both platforms in one integration.

Can I use a shortService foreground service for a voice call?

No. shortService has a hard ~3-minute cap and is designed for tasks like draining an outbox or finishing a sync. VoIP calls must use the phoneCall type, paired with microphone and camera if you’re transmitting audio/video. Telecom integration is strongly recommended for incoming calls.

How do I handle a deep link that arrives before the user has logged in?

Capture the intent in the Activity or Navigation graph, push the target destination onto a queue (SavedStateHandle or DataStore), and pop it after authentication completes. Do not lose the intent during a relogin redirect — we’ve seen countless meetings lost because the deep link was consumed by the login Activity and never re-delivered.

Do I still need POST_NOTIFICATIONS if my foreground service notification is ongoing?

Yes, on Android 13+. Without the runtime POST_NOTIFICATIONS grant, the ongoing notification still shows up in the FGS Task Manager but not in the regular notification shade. Users find that jarring and often report it as a bug. Request the permission as part of your onboarding flow.

ANDROID UX

Custom Android Call Notifications

Build the CallStyle notification your foreground service will pin to the shade.

ANDROID WEBRTC

Implement Screen Sharing in an Android App

MediaProjection + foreground service pattern for screen capture.

ANDROID AUDIO

Audio Output Switching on Android

Route audio between earpiece, speaker, and Bluetooth during a live call.

DISTRIBUTION

Distribute Android Apps Beyond Google Play

Alternative stores, direct APK, and how deep links behave outside Play.

Ready to ship an Android 14-clean call app this sprint?

Foreground services and deep links aren’t exotic anymore — they’re the first place Android 14/15 will punish a lazy implementation. Pick the right FGS type, declare the matching permission, wire CallStyle and Telecom for calls, and keep your service start-path away from background triggers. On the link side, retire every last firebase-dynamic-links import, publish a correctly-signed assetlinks.json, and pick a deferred-linking partner that matches your growth stack.

If you would rather hand the migration to a team that has done it a dozen times, Fora Soft builds Android apps with these patterns baked in. Bring your current manifest, Crashlytics dashboard, and link audit — we’ll give you a concrete plan in one call.

Want a 30-minute review of your FGS and deep-link stack?

We’ll look at your manifest, Crashlytics, and link flow, then sketch the highest-leverage fixes. No slide deck, no fluff.

Book a 30-min call → WhatsApp → Email us →

  • Development