<!-- skill: Android Background | version: 1.0.0 | description: WorkManager chains, foreground services and Doze-safe scheduling | category: Mobile -->
## Instructions
- Prefer `WorkManager` for deferrable work: `OneTimeWorkRequestBuilder<SyncWorker>().setConstraints(Constraints(NetworkType.CONNECTED, requiresBatteryNotLow=true)).build()`; enqueue with `enqueueUniqueWork("sync", ExistingWorkPolicy.APPEND_OR_REPLACE)`.
- Chain dependent work: `WorkManager.beginWith(upload).then(compress).then(cleanup).enqueue()`; pass data via `workDataOf("key" to value)` + `inputData`; use `Result.success(outputData)` to forward.
- Periodic sync: `PeriodicWorkRequestBuilder<SyncWorker>(15, MINUTES).setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, SECONDS)`; 15-min minimum enforced by OS, don't fight it.
- Foreground services for user-visible ongoing work (playback, tracking): `startForeground(id, notification, FOREGROUND_SERVICE_TYPE_LOCATION)` within 5s of start; declare type in manifest (`android:foregroundServiceType`).
- Notifications for FGS on API 26+: create `NotificationChannel` (`IMPORTANCE_LOW`) before `startForeground`; include cancel action (`PendingIntent` → stop service); update progress via `NotificationManager.notify`.
- Exact alarms need permission: `SCHEDULE_EXACT_ALARM` + `AlarmManager.setExactAndAllowWhileIdle(RTC_WAKEUP, triggerAt, pendingIntent)`; gracefully degrade to `setAndAllowWhileIdle` if permission denied (check `canScheduleExactAlarms()`).
- Doze/App Standby safe: use `setAndAllowWhileIdle` / `setExactAndAllowWhileIdle` for critical pings, FCM high-priority push to wake, `setRequiresDeviceIdle(false)` on workers; never poll with `Timer`/`Handler`.
- Expedited work: `setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)` for important one-shots that need foreground-like priority without full FGS.
- Cancellation + retries: `workManager.cancelUniqueWork("sync")` on logout; `Result.retry()` respects backoff; observe via `getWorkInfosForUniqueWorkLiveData` for UI state.
- Battery exemptions: request `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` only for core use-case (player/tracker) via settings intent; explain why or Play policy rejects.
- Testing: `WorkManagerTestInitHelper` + `SynchronousExecutor` for unit tests; `adb shell dumpsys jobscheduler` + `adb shell cmd jobscheduler run -f <pkg> <jobId>` to force-run; Doze test with `adb shell dumpsys deviceidle force-idle`.
- Manifest + permissions: declare `FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_LOCATION`/`DATA_SYNC`, `POST_NOTIFICATIONS` (API 33 runtime), `RECEIVE_BOOT_COMPLETED` for `BootReceiver` rescheduling.
## Examples
- Photo backup: compress → upload → cleanup chain, CONNECTED + charging constraints, retry with exponential backoff.
- Run tracker: FGS with location type + persistent notification + cancel action, survives Doze via wake-safe alarm checkpoints.
- Daily digest: `PeriodicWorkRequest` 24h flex + FCM tickle to trigger expedited sync, boot-rescheduled.
## Workflows
1. Classify work: deferrable → WorkManager, user-visible ongoing → FGS, exact-time → AlarmManager, push-triggered → FCM.
2. Implement Worker/Service with constraints + input/output Data + retry policy.
3. Wire notification channel + permissions + manifest service types.
4. Test: unit (TestInitHelper) → force-run via adb → Doze idle test → reboot reschedule test.
5. Battery review: check exemption need, backoff sanity, 15-min floor respected.
6. Ship: observe work infos in debug screen, log `runAttemptCount`, monitor WorkManager diagnostics.
## Templates
- Chain + periodic workers:
```kotlin
class UploadWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
  override suspend fun doWork(): Result {
    val path = inputData.getString("path") ?: return Result.failure()
    return try {
      val url = uploader.upload(path) // suspend fun
      Result.success(workDataOf("url" to url))
    } catch (e: IOException) {
      if (runAttemptCount < 3) Result.retry() else Result.failure()
    }
  }
}
// enqueue chain
val compress = OneTimeWorkRequestBuilder<CompressWorker>().build()
val upload = OneTimeWorkRequestBuilder<UploadWorker>()
  .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
  .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
  .build()
WorkManager.getInstance(ctx).beginWith(compress).then(upload)
  .enqueue()
// periodic
val daily = PeriodicWorkRequestBuilder<SyncWorker>(24, TimeUnit.HOURS)
  .setConstraints(Constraints.Builder().setRequiresBatteryNotLow(true).build())
  .build()
WorkManager.getInstance(ctx).enqueueUniquePeriodicWork("daily-sync", ExistingPeriodicWorkPolicy.KEEP, daily)
```
- Foreground service + alarm:
```kotlin
class TrackService : Service() {
  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    createChannel()
    val cancel = PendingIntent.getService(this, 0,
      Intent(this, TrackService::class.java).setAction("STOP"), PendingIntent.FLAG_IMMUTABLE)
    val notif = NotificationCompat.Builder(this, "track")
      .setContentTitle("Tracking run").setSmallIcon(R.drawable.ic_run)
      .addAction(R.drawable.ic_stop, "Stop", cancel).build()
    startForeground(1, notif, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
    if (intent?.action == "STOP") stopSelf()
    return START_STICKY
  }
  override fun onBind(i: Intent?): IBinder? = null
}
// exact alarm (API 31+ guard)
fun schedulePing(ctx: Context) {
  val am = ctx.getSystemService(AlarmManager::class.java)
  val pi = PendingIntent.getBroadcast(ctx, 0, Intent(ctx, PingReceiver::class.java),
    PendingIntent.FLAG_IMMUTABLE)
  if (Build.VERSION.SDK_INT < 31 || am.canScheduleExactAlarms())
    am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 3600_000, pi)
  else am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 3600_000, pi)
}
```
## Tools
- SARAH shell_run for `adb dumpsys jobscheduler/deviceidle`, `am start-foreground-service`, logcat WorkManager tags.
- SARAH file_write for Worker/Service/Receiver scaffolds; web_fetch for developer.android.com background guides.
## Advanced
- FCM wake: high-priority `data` payload → `onMessageReceived` enqueues expedited work; collapse keys to avoid storms; `setPriority(NotificationCompat.PRIORITY_HIGH)` only for urgent.
- Boot reschedule: `BootReceiver : BroadcastReceiver` on `BOOT_COMPLETED` re-enqueues unique periodic work; `directBootAware` if needed pre-unlock.
- Quotas: expedited + FGS launch limits on API 31+ (`foregroundServiceStartNotAllowedException`); catch and fall back to expedited work + notification.
## Documentation
- https://developer.android.com/topic/libraries/architecture/workmanager
- https://developer.android.com/develop/background-work/services/foreground-services
- https://developer.android.com/training/scheduling/alarms
- https://developer.android.com/training/monitoring-device-state/doze-standby
