Fetching latest headlines…
Architecting a location-aware sound manager without killing battery
NORTH AMERICA
🇺🇸 United StatesJuly 6, 2026

Architecting a location-aware sound manager without killing battery

0 views0 likes0 comments
Originally published byDev.to

It happened during a quiet afternoon at the library. I was deep into a debugging session for a client project when my phone decided it was the perfect moment to blast a ringtone at maximum volume. The entire room turned, faces filled with irritation. I fumbled to silence it, but in the panic, I hit the volume rocker too many times and put it on silent, missing an important call later that evening. That moment of public embarrassment was the final straw. I realized my phone, which is supposed to be a tool, was actively sabotaging my focus and social standing.

The problem

We all live in a constant state of toggling. We mute our phones before a meeting, remember to unmute them afterward, then inevitably forget to silence them again for a prayer, a lecture, or a medical appointment. Android provides manual controls, but the human element is the weak link. I wanted something that didn't require me to check a settings menu five times a day. I needed a system that understood context—where I was and what time it was—and adjusted the hardware state accordingly. The existing solutions were either too heavy, requiring constant GPS polling that decimated my battery, or too simple, lacking the logic to handle overlapping rules or emergency bypasses. I wanted a set-and-forget experience that felt like part of the OS, not a resource-heavy burden running in the background. My goal was to build a tool that respected both my schedule and my device’s energy efficiency, ensuring the silent mode was there when I needed it, and gone when I didn't, without me ever touching a button.

The technical decision / implementation

To build Muffle, I had to solve the classic Android paradox: how to stay location-aware without a constant Location service draining the battery. I initially experimented with LocationManager and the GPS_PROVIDER, but that was a mistake. It forces the hardware to stay active, leading to significant battery drain. Instead, I pivoted to the GeofencingClient within the Google Play Services library. This API is much more efficient because it offloads the monitoring to the system's location subsystem, which uses a mix of cellular towers and Wi-Fi access points rather than just raw GPS.

Even with GeofencingClient, the issue of triggering sound profiles remains. I needed a way to manage the transition between AudioManager states—like RINGER_MODE_SILENT, RINGER_MODE_VIBRATE, and RINGER_MODE_NORMAL—while respecting the user’s overrides. I implemented a custom ForegroundService to act as the central brain. By using a Foreground Service with a persistent notification, I ensured the OS wouldn't kill my process during memory pressure, which is critical for a background automation app. The core logic uses a high-priority queue to manage conflicting routines:

kotlin
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (shouldMute) {
audioManager.ringerMode = AudioManager.RINGER_MODE_SILENT
} else {
audioManager.ringerMode = AudioManager.RINGER_MODE_NORMAL
}

However, simply switching the ringer mode wasn't enough. I had to integrate NotificationManager.INTERRUPTION_FILTER_ALL versus INTERRUPTION_FILTER_PRIORITY to allow for "emergency bypass" contacts. By checking the user's whitelist against the incoming Bundle extras in my broadcast receiver, I could allow specific calls to ring through even when the system was set to Do Not Disturb. This hybrid approach—using the system's geofencing hardware for location and an internal priority queue for sound state—allowed me to keep battery consumption minimal while maintaining strict adherence to the user’s preferences.

What surprised you / what you'd do differently

What truly caught me off guard was how inconsistently manufacturers handle the AudioManager API. I assumed that a call to setRingerMode would be universal. I was wrong. Some OEMs, particularly those with aggressive battery optimization layers, would occasionally intercept the call or delay the execution of the state change if the phone was in a deep sleep state. I spent two weeks debugging why my phone wouldn't silence at my office location, only to realize that the BroadcastReceiver responsible for triggering the action was being throttled by the OS's power management policy.

To fix this, I had to move from simple broadcast receivers to a more robust WorkManager implementation for routine scheduling. WorkManager is much more "polite" to the system, but it also guarantees execution. If I were starting over, I would have avoided trying to build a custom "state engine" from scratch for the first version. I spent too much time trying to manage conflicting rules manually. I should have implemented a simple SQL-backed priority table from day one, allowing the database to be the source of truth for which routine wins in a conflict. Relying on volatile memory for routine state led to bugs where an active routine would simply 'forget' itself after a system reboot. Building a persistent storage layer early on would have saved me hundreds of hours of debugging inconsistent states and edge-case failures.

Practical takeaway

If you are building an app that relies on background triggers, the biggest lesson I learned is to never trust the OS to keep your process alive indefinitely. Always assume the user's phone will kill your background tasks to save battery, and architect for that reality. Use WorkManager for tasks that need to happen reliably, and use the system’s built-in APIs like GeofencingClient instead of trying to roll your own location listeners. Don't fight the Android ecosystem; work with the constraints it provides. If you need to manage sound or system settings, check the NotificationManager policies early and often, because user settings in those menus will often override your app's commands without throwing an error. By focusing on the user’s intent rather than just the code execution, you can build tools that feel native and reliable. I’ve tried to incorporate these lessons into my own utility, Muffle, which handles these exact logic flows for automating sound profiles based on location or time. You can see how I approached these problems in the source-adjacent logic at https://play.google.com/store/apps/details?id=com.muffle.app.

Comments (0)

Sign in to join the discussion

Be the first to comment!