Opening hook
The silence of the boardroom was absolute, the kind of stillness that precedes a major decision. I was presenting a project scope to a client, focused entirely on the whiteboard. Suddenly, my pocket erupted with a frantic, rhythmic vibration that sounded like a jackhammer against the mahogany table. I fumbled for my device, my face burning, while the client stared at the ceiling. I had remembered to silence my notifications for the gym, but I completely forgot that my commute triggered a location-based profile meant for my house. It was a classic human error caused by a lack of automation.
The problem
We all live in a state of constant, low-level anxiety regarding our digital footprint. We are expected to be reachable, yet we are also expected to be invisible during specific life events like prayer, meetings, or deep-focus study sessions. The friction here isn't just about silence; it's about the cognitive load of manual management. Android offers Do Not Disturb modes, but they are often blunt instruments. If you rely on time-based schedules, you are locked into a rigid structure that fails the moment your calendar shifts or traffic delays your arrival.
I wanted a system that was truly context-aware. I didn't want to manage switches; I wanted a state machine that reacted to where I was and what I was doing. The problem with existing solutions was their heavy-handed approach to battery consumption. Most apps that attempt location-based triggers end up polling the GPS coordinates every few minutes, effectively turning the device into a space heater and draining the battery before lunch. I needed a way to automate sound profiles without turning my phone into a brick, and that required a deep dive into the Android system's architecture.
The technical decision / implementation
When I started building Muffle, I initially considered using a simple LocationListener with a high-frequency update interval. That was a mistake. Monitoring location in the background is one of the fastest ways to get your process killed by the Android OS or hated by your users. Instead, I pivoted to the GeofencingClient within the Google Play Services library. This API is significantly more power-efficient because it offloads the heavy lifting to the hardware-assisted location provider.
By defining a GeofencingRequest with a specific radius around my points of interest, I moved the responsibility from my app's process to the underlying Android system service. The system monitors the geofences at the firmware level, and only wakes my app when the transition—entering or exiting—occurs. To keep things stable, I implemented a PendingIntent that broadcasts to a BroadcastReceiver in my app, which then triggers the AudioManager to modify the volume state.
kotlin
val geofence = Geofence.Builder()
.setRequestId("office_location")
.setCircularRegion(latitude, longitude, 100f)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.build()
val request = GeofencingRequest.Builder()
.addGeofence(geofence)
.build()
geofencingClient.addGeofences(request, geofencePendingIntent)
This architecture keeps the main process dormant. The PendingIntent pattern is crucial here because it allows the app to be completely closed while the system handles the proximity detection. I also had to account for cases where the user moves quickly, such as driving, which is why I set a minimum radius of 100 meters. Anything smaller caused the hardware to flip-flop between states due to GPS drift, leading to a frustrating experience where the phone would vibrate and silence itself repeatedly while I was just passing by a location.
What surprised you / what you'd do differently
What truly caught me off guard was the inconsistency of location updates across different Android OEMs. I spent weeks debugging why my geofences wouldn't trigger on certain devices, only to realize that aggressive battery optimization settings on non-Pixel devices were killing my BroadcastReceiver despite it being registered correctly. This is the 'dirty secret' of Android development: the OS documentation says one thing, but manufacturer-specific modifications do another.
I assumed that if I requested the proper permissions and used the standard GeofencingClient, the system would respect the request. I was wrong. I had to implement a manual check that guides the user to the 'Battery Optimization' settings page for their specific device. It isn't an elegant solution, but it is a necessary one. If I were starting over, I would build a much more robust abstraction layer to handle these 'state checks' earlier in the onboarding process.
Another surprise was the impact of cellular tower switching. Sometimes, the device would report a transition based on network-based location when the GPS signal was weak, like inside a building. If I had to rewrite the engine, I would implement a custom filter that validates the accuracy of the location broadcast before applying any sound changes. Relying solely on the GeofencingClient callback without a secondary validation check can lead to false positives, which is why I eventually added a logic gate that checks the accuracy radius provided by the Location object before executing the AudioManager commands.
Practical takeaway
If you are building any location-aware features, start by assuming that the OS will be your biggest enemy regarding battery and process persistence. Stop trying to poll location manually and lean into the GeofencingClient or FusedLocationProviderClient. Your goal should always be to stay out of the foreground as much as possible. If your app requires a persistent notification to stay alive, you are likely doing something that the OS is trying to optimize away.
Always design for the edge case where the user has no signal, or where they are moving between different Wi-Fi access points that might confuse the network location provider. Build your app to be resilient; expect the system to kill your background tasks at any moment. If you can build a state machine that recovers gracefully after a reboot or a process termination, you are already ahead of most applications.
I have spent a significant amount of time refining these routines to ensure they work without draining the battery, and you can see how I approached the final implementation at https://play.google.com/store/apps/details?id=com.muffle.app. Solving the automation problem for sound profiles taught me more about Android's power management system than any tutorial ever could. Keep building, and keep your code clean.
Top comments (0)