Skip to Content
Getting StartedPlatform SetupAndroid SDK

Android SDK: Taming the Battery Manager

Android is infamous for aggressively killing background services. Chinese OEMs (like Xiaomi, Huawei) even have custom power managers that completely break standard Android background execution limits.

This page explains why Android kills your app and how Tracelet’s specific Android configurations solve it in the real world.


Optional dependency: high-accuracy GMS location

Tracelet’s Android SDK can use Google Play Services LocationFusedLocationProviderClient (better accuracy and battery), hardware activity recognition (walking / driving / still), and hardware geofencing. To keep the SDK lightweight and usable on Google-less devices, this dependency is not bundled. You add it only if you want the high-accuracy path:

// android/app/build.gradle.kts dependencies { implementation("com.google.android.gms:play-services-location:21.3.0") }

It’s optional, and the SDK degrades gracefully. Without play-services-location, Tracelet falls back to the standard AOSP LocationManager (plain GPS). Tracking still works — you just lose the fused provider’s accuracy/battery benefits, hardware activity recognition, and hardware geofencing. Recommended: add it unless you specifically target Google-less / AOSP devices (e.g. Huawei without GMS, or de-Googled ROMs).

Minimum version: 21.2.0. Tracelet’s Android code calls the interface-based FusedLocationProviderClient and ActivityRecognitionClient APIs, which only became interfaces in play-services-location 21.2.0. Older releases (e.g. 19.0.0) ship these as concrete classes, so calling into them throws java.lang.IncompatibleClassChangeError at runtime. Tracelet publishes a Gradle dependency constraint that raises play-services-location to 21.2.0+ automatically if another dependency pulls in an older version, but if you pin the version yourself, keep it at 21.2.0 or newer (we recommend 21.3.0).

With play-services-locationWithout it (AOSP fallback)
Fused location (best accuracy + battery)Raw GPS / network via LocationManager
Hardware activity recognition (onActivityChange)Accelerometer-only motion detection
Hardware geofencingSoftware (in-SDK) geofence evaluation

Optional dependency: Play Integrity (device attestation)

If you use Tracelet’s device attestation feature (AttestationConfig), the Android side generates its cryptographic token through Google Play Integrity. Just like play-services-location, this dependency is not bundled — you add it only when you enable attestation:

// android/app/build.gradle.kts dependencies { implementation("com.google.android.play:integrity:1.6.0") }

It’s optional, and attestation degrades gracefully. Without the Play Integrity dependency, everything else in Tracelet works normally — only attestation is affected: AttestationConfig(enabled: true) logs a warning and Tracelet.getAttestationToken() returns null instead of a token, rather than crashing. Add the dependency only if you enable attestation and need the Play Integrity verdict on Android. See the Device Attestation guide  for the full workflow and server-side verification.

It must be implementation, not compileOnly. Play Integrity is compileOnly inside the Tracelet SDK (so it is never shipped or forced on apps that don’t use attestation). If you enable attestation without adding the dependency to your app module as implementation, the classes are absent at runtime and attestation silently returns null.


Permissions & Manifest Setup

Tracelet automatically injects the necessary permissions into your AndroidManifest.xml when you compile your app. By default, it requests:

  • ACCESS_COARSE_LOCATION & ACCESS_FINE_LOCATION (Required for tracking)
  • ACCESS_BACKGROUND_LOCATION (Required for tracking when app is closed)
  • FOREGROUND_SERVICE & FOREGROUND_SERVICE_LOCATION (Required for continuous background execution)
  • ACTIVITY_RECOGNITION (Required for the smart motion-detection engine)
  • POST_NOTIFICATIONS (Required for Android 13+ foreground service UI)
  • SCHEDULE_EXACT_ALARM (Required for precise Periodic Mode tracking)

Removing Unused Permissions (Manifest Merger)

If your app doesn’t need a specific feature (for example, your client doesn’t need motion detection, or you don’t use Periodic Mode), you can forcefully remove the permission using Android’s manifest merger tools:node="remove" directive in your app-level AndroidManifest.xml (android/app/src/main/AndroidManifest.xml).

Tracelet’s native code is fully guarded with checkSelfPermission(). Missing permissions will trigger graceful fallbacks, not crashes.

PermissionEffect when removed (tools:node="remove")
ACTIVITY_RECOGNITIONSafe. Falls back to basic accelerometer-only motion detection. The onActivityChange stream won’t fire (no walking/running/driving classification).
ACCESS_BACKGROUND_LOCATIONSafe. Android 10+ will restrict tracking when the app is completely backgrounded or swiped away.
SCHEDULE_EXACT_ALARMSafe. Periodic mode falls back to using battery-friendly, but inexact, WorkManager timers.
POST_NOTIFICATIONSSafe. The foreground service persistent notification is hidden on Android 13+. Service still runs.
REQUEST_IGNORE_BATTERY_OPTIMIZATIONSSafe. You won’t be able to request battery optimization exemptions from the OS.
FOREGROUND_SERVICE / FOREGROUND_SERVICE_LOCATIONSafe for geofence-only / periodic-only apps. Removing them disables continuous foreground tracking (start()), but standard geofencing and periodic mode keep working. Required for continuous background tracking.
ACCESS_FINE_LOCATION / COARSEUNSAFE. Without any location permission, Tracelet cannot obtain positions.

Google Play foreground-service policy (effective Oct 28, 2026). Geofencing is no longer a permitted use case for foreground service location. If your app uses a foreground service solely for geofencing, you must remove FOREGROUND_SERVICE_LOCATION (and FOREGROUND_SERVICE) from your merged manifest. Tracelet’s standard geofence mode uses the native Geofence API and does not start a foreground service, so geofence-only apps are compliant — just strip the permissions below and keep foregroundService.enabled = false. See the Geofencing guide for details.

<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <!-- Geofence-only / periodic-only app: no continuous foreground tracking --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" tools:node="remove" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" tools:node="remove" /> </manifest>

Example: Removing Motion & Background Permissions

<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <!-- Client doesn't want Activity/Motion tracking --> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" tools:node="remove" /> <!-- Client only wants foreground tracking --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" tools:node="remove" /> <!-- Remove Exact Alarms --> <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" tools:node="remove" /> </manifest>

Automatic ProGuard / R8 Rules

Tracelet heavily utilizes background Kotlin services, JNA bindings for the Rust core, and reflective Pigeon channels. If these are obfuscated or shrunk incorrectly, background tracking will silently fail when your app is compiled for release.

You do not need to manually configure ProGuard for Tracelet.

The tracelet_android plugin automatically ships with embedded consumer-rules.pro. When you build your Flutter app in release mode, Android’s R8 shrinker automatically extracts these rules and ensures all necessary classes (such as the HeadlessTaskService, BootReceiver, and the Rust uniffi bindings) survive shrinking.


Scenario 1: The Delivery Driver (Continuous Tracking)

Concepts Explored: Doze Mode, Foreground Services, Wakelocks

The Problem

Your user is a pizza delivery driver. They put the phone in their pocket, turning off the screen. After 15 minutes, Android enters Doze Mode. It shuts down network access, defers background jobs, and severely limits CPU wake-ups to save battery. If your app relies on a simple timer to get GPS, Android will simply refuse to run your timer.

How Tracelet Solves It: Foreground Service

A Foreground Service tells Android: “Hey, I am doing something incredibly important right now, and the user is fully aware of it. Do not kill me.”

android: tl.AndroidConfig( foregroundService: tl.ForegroundServiceConfig( enabled: true, channelName: 'Delivery Tracking', notificationText: 'Tracking your route to the customer', notificationOngoing: true, // User cannot swipe it away showNotificationOnPauseOnly: false, // Auto-hides when app is open actions: ['Pause', 'Complete'], // Adds interactive buttons to the notification ), )

By showing a persistent notification in the status bar, Tracelet elevates your app’s priority to almost the level of the foreground UI. Android will let Tracelet run indefinitely, bypassing Doze Mode restrictions.

Customizing the Notification Appearance

To make the persistent notification match your app’s branding, you can customize the icons and colors:

android: tl.AndroidConfig( foregroundService: tl.ForegroundServiceConfig( enabled: true, notificationTitle: 'Delivery Mode', notificationText: 'Tracking your route...', notificationColor: '#0F9D58', // Your brand hex color notificationSmallIcon: 'ic_tracelet_icon', // The icon name notificationLargeIcon: 'ic_large_logo', ), )

Important Icon Rules:

  1. File Location: The image file must be placed in your Android project’s drawable folder (android/app/src/main/res/drawable/ic_tracelet_icon.png).
  2. Small Icon Constraints: Android requires the notificationSmallIcon to be completely flat, transparent, and white-only. If you use a colored logo, Android will simply render it as a solid gray or white square.
  3. Color Property: The notificationColor property will tint the background of your small icon to match your brand.
🔔

Android 13+ Note: You must request the POST_NOTIFICATIONS permission before starting the service, otherwise the notification will be silently suppressed by the OS. See the Flutter SDK: Permissions page for exactly how to request this from your Dart code.

Refreshing the Notification While Tracking (updateNotification())

Need to change the notification after tracking has already started — e.g. update the title from “En route” to “Arriving”, swap the text, or change the action buttons mid-trip? Calling setConfig() with a new ForegroundServiceConfig persists the values, but a notification-only change does not repost the notification that Android is already showing — the new content would only appear after an unrelated service restart or foreground transition.

Since v3.6.8, Tracelet.updateNotification() refreshes the live notification in place, without restarting the tracking pipeline:

// 1. Apply the new notification content. await Tracelet.setConfig( const tl.Config( android: tl.AndroidConfig( foregroundService: tl.ForegroundServiceConfig( notificationTitle: 'Arriving', notificationText: 'Your driver is 2 minutes away', ), ), ), ); // 2. Repost the active notification with the new content. await Tracelet.updateNotification();

Safe by design. updateNotification() never restarts tracking, and it is a no-op when the foreground service is not currently running (nothing to refresh). On iOS it refreshes the running Live Activity instead (when you opted into one via liveActivityConfig); on web it is a no-op — so the same call is safe on every platform.


Scenario 2: The Weather App (Periodic Tracking)

Concepts Explored: WorkManager vs. Exact Alarms

The Problem

Your user downloaded your local weather app. You want to wake up in the background once every 4 hours, get their location, and download the local forecast.

You do not want a persistent Foreground Service notification. The user would hate seeing a permanent “Weather tracking” notification in their drawer.

How Tracelet Solves It: Periodic Mode

Instead of a continuous service, Tracelet can use Android’s job schedulers to wake up briefly, get a fix, and go back to sleep.

android: tl.AndroidConfig( periodicUseForegroundService: false, periodicUseExactAlarms: true, // Uses AlarmManager instead of WorkManager )

The Two Schedulers

  1. WorkManager (periodicUseExactAlarms: false) The default. It is very battery friendly. However, Android decides when to run it. If you ask it to run every 15 minutes, Android might wait 45 minutes and run it when the user unlocks their phone to check Instagram (called “batching”). It is highly inexact.

  2. AlarmManager (periodicUseExactAlarms: true) If you absolutely MUST wake the device up exactly at a specific time, use this. It requires you to declare the SCHEDULE_EXACT_ALARM permission in your android/app/src/main/AndroidManifest.xml:

    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />

    Note: Google Play strictly reviews this permission. Only use it if your app is an alarm clock, calendar, or absolutely requires exact timing.


Scenario 3: The OEM Aggression

Concepts Explored: Settings Health API

The Problem

You did everything right. You have a Foreground Service. But the user owns a Xiaomi phone. MIUI has a proprietary “Battery Saver” that force-kills even Foreground Services after 5 minutes of screen-off time.

How Tracelet Solves It: Auto-Mitigation and Prompts

Tracelet automatically applies internal mitigations where possible, but sometimes the user physically has to whitelist your app in the OEM’s proprietary settings menu.

final health = await tl.Tracelet.getSettingsHealth(); if (health['isAggressiveOem'] == true) { // Automatically opens the manufacturer-specific settings screen // (e.g. Xiaomi Autostart, Huawei App Launch, Samsung Sleeping Apps) await tl.Tracelet.showPowerManager(); }

For a detailed example of how to implement this in your Flutter UI, see the Diagnostic Tools & Power Management page.