EN BG DE

← All articles

Honor's isIgnoringBatteryOptimizations() returns true in foreground and false in background — for the same app, at the same time

I maintain an Android app that monitors elderly people and emails their family if something looks wrong. It runs a foreground service 24/7, and the single most important thing it does at install time is get added to the battery-optimisation whitelist - the one behind PowerManager.isIgnoringBatteryOptimizations().

On my Honor device running MagicOS 9.0, I did a fresh new install. I did everything right during setup. Tapped "Don't optimise." Enabled autostart. Finished onboarding. All looked good. I opened the app a few times, closed it. Normal behavior. People will open it, see that it works, close it. They will wake up, out of curiosity, they will open it, then close it.

A few hours later, I received an email:
Battery optimisation is blocking the app. Please open Settings and set it back to "Don't optimize."

I opened the OS settings and all looked good there. They were correct.

The logs showed something strange:

2026-05-01 19:27 UTC  [WatchdogWorker]  isIgnoringBatteryOptimizations = false
                                         → health = Degraded(battery_optimization_active)
2026-05-02 09:25 UTC  [MonitoringAutoRecover]  isIgnoringBatteryOptimizations = true
                                                → health = Healthy

Same device. Same package. Same API call: context.getSystemService(PowerManager::class.java).isIgnoringBatteryOptimizations(packageName).

The watchdog runs as a background process. No activity, no visible UI at that moment.
The auto-recover check runs when the user opens the app. It's a foreground process. Activity visible, window focused.

Between 19:27 and 09:25, the watchdog fired roughly every thirty minutes. Every single background check returned false. I opened the app the next morning, the foreground check returned true, the app said "everything's fine," and the recovery screen never appeared.

What Honor is actually doing

AOSP's isIgnoringBatteryOptimizations() reads the deviceidle whitelist in DeviceIdleController. That's a persistent list. You get added via ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS or dumpsys deviceidle whitelist +<package>. Once you're on it, you stay on it until someone removes you.

Honor's PowerGenie sits on top of this. It has its own internal classification - a per-UID trust score that evolves based on the app's runtime behaviour (job frequency, wake patterns, GPS usage, the resurrection-detection heuristic I wrote about previously). When PowerGenie decides your trust is low enough, it doesn't remove you from the AOSP whitelist. That would be visible in dumpsys. Instead, it folds an enforcement bit into the return value of isIgnoringBatteryOptimizations().

And here's the part that cost me two days: the enforcement bit is only applied when the caller is a background process. When the calling process has a visible activity — when the user is looking at the app — PowerGenie shadows the read and returns the "real" AOSP whitelist value. Which is true, because the user never revoked the exemption.

In other words:

  • Background worker asks "am I whitelisted?" → PowerGenie says no
  • User opens the app, same code asks "am I whitelisted?" → PowerGenie says yes
  • The AOSP whitelist never changed

I spent two days convinced this was a race condition before I looked at the timestamps carefully enough to see that every single background read disagreed with every single foreground read, for sixteen straight hours. That's not a race. That's policy.


The app is How Are You?! — elderly monitoring via AI pattern learning. If you've hit a vendor API that returns different values depending on the calling context, I'd genuinely like to know about it. The AOSP contract says nothing about this, and I suspect Honor isn't the only one doing it.