The Boot

Leejaywaggoner
3 min readFeb 27, 2024

--

Photo by Joey C on Unsplash

I wanted to try a dev log, so I started taking notes on my latest personal app. It’s a mileage tracker. I know, there are plenty out there, but mine doesn’t — and never will — store your location data anywhere but on your own device. The highest form of security. Someone might buy that for a dollar. 🙂 👍

The requirements are as follows:

  • The app should only track your miles when you’re driving.
  • The app must always track your miles, even if the app is in the background or has been closed.
  • If the phone is turned off, the app should be ready to track miles as soon as it’s rebooted, even if the app has not been restarted.
  • The user should be able to view, rename or delete all recorded drives.

I initially thought about using the Google Maps API to display the user’s location during a drive, but it’s really such a pain in the ass to get an API key now, that I decided not to make it part of the MVP. I already used it on a recent project, so there’s not much I could learn right now that I don’t already know. Maybe later.

The plan is to use Google’s Activity Recognition API to trigger the location tracking when the device detects that the user is driving, Google’s Location APIs for the mileage tracking from a foreground service — which is a strange thing to call a service that’s essentially running in the background — and the Android Room database library to store the drive data locally. The first task I decided to tackle was handling the reboot requirement. After all, how hard could it be, be, be, be..?

I created my project with targetSdk=34 and minSdk=28, and my manifest looked something like this:

<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
...
<receiver
android:name=".domain.services.OnBootReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
</application>

</manifest>

My BroadcastReceiver looked like this:

class OnBootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.d(
"${OnBootReceiver::class.simpleName}",
"Received broadcast: ${intent?.action ?: "null action"}"
)
}
}

Aaaand… It didn’t work. At least, I thought it didn’t work. On reboot, I never saw the message show up in my logcat, so I went searching for an answer.

I started thinking, maybe there was something wrong with the intent filter, so I googled around and people had all kinds of crazy things to say on the subject, so mine ended up looking like this:

<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>

I still wasn’t seeing any log messages, so I looked into the BroadcastReceiver a little. Maybe I needed to add the permissions attribute to the <receiver> tag, so now it looked like this:

<receiver
android:name=".domain.services.OnBootReceiver"
android:exported="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
</intent-filter>
</receiver>

Which still didn’t work, and was pointless to boot (pun intended). The BroadcastReceiver docs clearly state that, “If this attribute isn’t set, the permission set by the <application> element's permission attribute applies to the broadcast receiver.”, and I had already set the permission in the <application> element.

Now I was out of ideas so I decided to go ahead and have it start a foreground service, just to see what would happen. Sure enough, as soon as I rebooted this time, the log showed up. Weird. Next, I stripped out all the crap I had added and went back to my original code with the service still added, and, yep. I still got the log confirmation that it was working. Weird.

Later on, I replaced the foreground service launch with a call to the Google Activity Recognition Transition APIs startup code, and I kept seeing the log. So, now I have the ability to know when a drive has started, even after a reboot without an app restart.

Update: Out of curiosity, I removed all the code except the log call from the broadcast receiver to see if I still wouldn’t see the log, but now I can. Weird. Again. Whatever. It works. Next, I’ll figure out when the user is driving.

--

--

Leejaywaggoner

A seasoned software developer with decades of experience, sharing deep insights and industry wisdom.