Opening hook
It happened during a quiet afternoon at the mosque. The imam was mid-sentence when a rhythmic, high-pitched ringtone cut through the silence like a knife. Every head turned. It was my phone. My heart sank as I scrambled to silence it, only to realize I had forgotten to flip the physical toggle before walking in. That moment of collective, disappointed glares burned. It wasn't just an annoyance; it was a total breakdown of my focus and a social failure I had accidentally caused because my phone couldn't manage itself.
The problem
We live in an era where our devices are supposedly 'smart,' yet they are remarkably bad at knowing when to keep quiet. We carry computers in our pockets that can calculate the exact position of the moon or stream 4K video, but they cannot inherently tell that we are in a meeting, a lecture, or a place of worship. You could argue that setting a manual schedule works, but life isn't static. Meetings run over, prayer times shift by a minute each day based on astronomical calculations, and spontaneous plans happen.
I found myself constantly juggling the physical volume buttons. If I remembered to mute it, I inevitably forgot to unmute it afterward, missing urgent calls from family. If I didn't mute it, I was the person disrupting the room. I wanted a solution that respected the context of my location and the specific time of day without requiring me to touch my screen. The core friction is that Android is designed to restrict background processes to save battery, which is exactly what a silent-automation app needs to thrive. Getting the app to reliably trigger a volume change while the phone is sitting in a pocket, deep in Doze mode, became my primary development hurdle.
The technical decision / implementation
When I started building Muffle, I initially tried a standard Service with a Handler loop to check conditions. It worked fine while the screen was on, but as soon as the phone entered Doze mode, the OS aggressively throttled my CPU cycles. The AlarmManager became my best friend, but not in the way many tutorials suggest. I had to move away from inexact repeating alarms and embrace setExactAndAllowWhileIdle.
The real architectural challenge was balancing the need for accuracy with the platform's battery-saving constraints. If I requested too many updates, the system would flag Muffle as a battery hog. I settled on a hybrid approach: I use AlarmManager for time-based triggers and the GeofencingClient for location-based state changes. The Geofencing API is efficient because it relies on the hardware-level location providers, offloading the heavy lifting from my code to the Google Play Services layer. For the prayer times, I calculate the upcoming event and schedule a single precise alarm for that specific moment, rather than polling the time every minute.
kotlin
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, MuffleReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
triggerTimeMillis,
pendingIntent
)
This snippet forces the OS to wake the CPU specifically for my trigger. By using RTC_WAKEUP, I ensure that even if the phone has been idle for hours, the BroadcastReceiver fires, allowing me to interact with the AudioManager to toggle the ringer mode. I avoid long-running background services entirely, preferring to wake up, perform the state change, log the action to my local room database, and immediately release the WakeLock. This 'hit-and-run' architecture keeps the app off the battery usage blacklist while maintaining the reliability that users expect from a tool designed for sensitive environments.
What surprised you / what you'd do differently
What truly blindsided me was how inconsistently manufacturers handle Doze mode. I built the app on a Pixel, where setExactAndAllowWhileIdle behaves exactly as the documentation says. When I started beta testing on devices from other manufacturers—specifically those with aggressive 'battery optimization' layers—my alarms weren't firing at all. I had initially assumed the Android OS provided a uniform environment, but I learned that some OEMs essentially kill any process that isn't a foreground service or a system app, regardless of what the standard APIs dictate.
I spent weeks debugging why my background triggers were 'missing' on certain hardware. The fix wasn't in the code; it was in the user experience. I had to build a diagnostic screen that checks if the app is excluded from battery optimizations and prompts the user to manually adjust their settings. If I were starting over, I would have prioritized the 'Power Management' documentation much earlier in the cycle. I spent too much time perfecting the logic of the routines and not enough time anticipating the hostile environment that is the modern Android ecosystem. I also learned that relying on AlarmManager for everything is a trap. Sometimes, a simple WorkManager task, even with its inherent latency, is more stable because it respects the system's own resource scheduling rather than trying to fight against it. I had to learn that 'exact' is a request, not a guarantee, and coding with that humility saved my app's reliability.
Practical takeaway
If you are building an app that requires background execution, stop trying to fight the Android OS. The days of keeping a service running indefinitely to perform tasks are gone. Instead, embrace event-driven architecture. Use WorkManager for tasks that don't need to be immediate, and use AlarmManager only when you absolutely need to wake the device for a specific user action.
Always provide clear, user-facing instructions on how to handle battery optimizations. Acknowledging that your app might be killed by the OS isn't a sign of failure; it is a sign of being a responsible developer who respects the user's battery life. Your goal is to be a utility that gets in and out of the CPU as quickly as possible. When you write code that is respectful of resources, the OS is much more likely to let you do what you need to do when it matters most. To see how these principles look in practice, you can view the result of this architectural approach at https://play.google.com/store/apps/details?id=com.muffle.app
Top comments (0)