Mobile Development Bug Report

Background location stopped when the screen turned off on Android 14: foreground service types

Our delivery app tracks a driver's route during a delivery. After targeting Android 14, tracking stopped minutes after the screen turned off, and on some devices the app crashed when tracking started. Android 14 requires a declared foreground service type and permission. How we fixed it.

Marcus Lindqvist Marcus Lindqvist @marcuslindqvist · December 21, 2025 · 7 min read · 23 Views
Banner: background location stopped when the screen turned off on Android 14, foreground service types

Our delivery app records the driver's route while a delivery is in progress, so customers can see the driver approaching and support can resolve disputes. After a release that raised targetSdkVersion to 34 (Android 14), the operations team reported routes with large gaps: the dot froze on the map a few minutes after drivers locked their phones and jumped ahead when they unlocked them.

A smaller number of drivers reported something worse: the app crashed the moment they tapped "Start delivery".

The crash

From a crash report for an Android 14 device:

android.app.MissingForegroundServiceTypeException: Starting FGS without a type
  callerApp=ProcessRecord{...com.example.delivery} targetSDK=34
    at android.app.MissingForegroundServiceTypeException$1.createFromParcel(...)
    at android.app.ActivityManager.startForegroundService(...)
    at com.example.delivery.tracking.TrackingService.onStartCommand(TrackingService.kt:44)

Our tracking module (a small native module plus a location library) started a foreground service to keep location updates running with the screen off. On Android 14, with target SDK 34, starting a foreground service without declaring its type throws.

The gaps

On devices where the service did start (because the library's own service declared a type, but our manifest merge had altered it), we saw the other symptom: location stopped arriving after a few minutes. Checking the service state with the screen off:

adb shell dumpsys activity services com.example.delivery | grep -E "isForeground|foregroundServiceType"
    isForeground=false foregroundId=0 foregroundNoti=null

The service had been started but never promoted to foreground. Without a foreground service, Android restricts background location access heavily, and app processes in the background are eligible to be stopped.

What we ruled out

  • Location permission denied. The app had ACCESS_FINE_LOCATION, and drivers had granted it. For a foreground service started while the app is visible, background location permission is not required.
  • GPS hardware or signal. Unlocking the phone resumed updates instantly.
  • A JavaScript timer problem. The JS code only received updates; the gap existed in the native location callbacks too.

Root cause

Since Android 14, apps targeting API 34 must declare a type for every foreground service in the manifest and hold the matching permission, such as FOREGROUND_SERVICE_LOCATION for location. Our service was declared without android:foregroundServiceType="location", and the type-specific permission was missing, so starting it as a foreground service failed (crash) or the start path that caught the error left it in the background (gaps).

Foreground services on recent Android versions

Version Change relevant to location tracking
Android 10 Background location requires ACCESS_BACKGROUND_LOCATION; foreground services need foregroundServiceType="location" to access location
Android 12 Apps cannot start foreground services from the background, with exceptions
Android 14 (target 34) Every foreground service must declare a type and hold a type-specific permission

A foreground service shows a persistent notification and tells the system the user is aware of ongoing work. In return, the app keeps running and can access location while the screen is off.

Starting the service correctly

The service must call startForeground() with the type shortly after being started, and on Android 14 the app must be in a state where starting a location foreground service is allowed: typically, the user just tapped a button in the visible app, and the app holds location permission.

Who is affected

Any app that raised targetSdkVersion to 34 or later and starts foreground services through native code or a library that has not been updated. React Native location and tracking libraries released before Android 14 support, and custom services in the app's own Android code, are the usual sources.

Environment

Component Version / value
React Native 0.79, New Architecture
Target SDK 34 (raised from 33)
Tracking Custom native module with a Service, fused location provider
Devices Android 14 and 15 crashed or stopped; older versions worked

Manifest: declare type and permissions

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application>
        <service
            android:name=".tracking.TrackingService"
            android:exported="false"
            android:foregroundServiceType="location" />
    </application>
</manifest>

POST_NOTIFICATIONS (Android 13 and later) is needed so the foreground service notification is visible; the app asks for it before the first delivery.

Service: start in the foreground with the type

class TrackingService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Delivery in progress")
            .setContentText("Sharing your location with the customer")
            .setSmallIcon(R.drawable.ic_route)
            .setOngoing(true)
            .build()

        ServiceCompat.startForeground(
            this, NOTIFICATION_ID, notification,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
                ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION else 0
        )
        startLocationUpdates()
        return START_STICKY
    }
}

ServiceCompat.startForeground handles the API differences. The service starts when the driver taps "Start delivery", while the app is in the foreground, which satisfies the start restrictions.

Location request tuned for battery

val request = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10_000L)
    .setMinUpdateDistanceMeters(15f)
    .setWaitForAccurateLocation(false)
    .build()
fusedClient.requestLocationUpdates(request, callback, Looper.getMainLooper())

Ten seconds and 15 metres were enough for the customer map and cut battery use compared with our previous two-second interval.

Manufacturer battery optimisation

Some manufacturers add aggressive background restrictions beyond stock Android. For drivers, who use company-managed devices, the device management profile excludes the app from battery optimisation. For personal devices, the app links to the system's battery optimisation settings with an explanation, rather than requesting the exemption silently.

React Native side

The JavaScript only starts and stops tracking through the native module and displays updates; it does not need to run while the screen is off, because the native service records points to local storage and uploads them in batches.

Alternatives and trade-offs

  • Updated third-party tracking libraries that declare service types correctly. We evaluated two; either would have worked, but our custom module was small enough to fix.
  • Background location permission with geofencing or periodic work instead of a foreground service. Unsuitable for continuous route tracking and harder to justify in Play Store review.

How to test the fix

adb shell dumpsys activity services com.example.delivery | grep -E "isForeground|types="
# isForeground=true ... types=0x00000008 (location)
adb shell input keyevent KEYCODE_POWER      # screen off
sleep 600
adb logcat -d | grep TrackingService | tail -3   # updates still arriving

We also ran a two-hour drive test on Android 14 and 15 devices from three manufacturers with the screen off.

Confirming in production

After the release, route gaps longer than 60 seconds dropped from about 30% of deliveries on Android 14 and later to under 2%, mostly in tunnels. The MissingForegroundServiceTypeException crash disappeared from crash reports.

Regression guard

  • A CI check parses the merged manifest of release builds and fails if any <service> that calls startForeground lacks foregroundServiceType.
  • Raising targetSdkVersion is a separate pull request with a checklist from the Android behaviour changes page.

Security implications

Continuous location is sensitive personal data. The persistent notification makes tracking visible to the driver, tracking runs only during an active delivery, and the Play Store data safety form and in-app explanation describe exactly what is collected and why. Stored route points are deleted after 30 days.

Prevention checklist

  • Declare foregroundServiceType for every foreground service when targeting Android 14.
  • Add the matching FOREGROUND_SERVICE_* permission.
  • Start location foreground services from a visible user action.
  • Request notification permission so the ongoing notification is shown.
  • Test tracking with the screen off on recent Android versions before raising the target SDK.

References

Add a label

#android #react-native #location #foreground-service #battery
Written by
Marcus Lindqvist @marcuslindqvist

I have spent most of the last decade on mobile teams, first native iOS, then React Native and Flutter for companies that needed one team to ship to both stores. The work I enjoy most is the unglamorous part between "it runs on my phone" and "it is live for a million users": code signing, release builds, store review, crash triage and background behaviour that differs on every Android vendor. Most of the bugs I write about here cost a release day. Each post shows the exact error, what we ruled out, the fix and how we proved it in the next build, so you can get your own release out the door sooner.

Advertisement

Comments

0

No comments yet. Be the first to comment!

Sign In to post a comment.