Running on Android BOOT_COMPLETED

randomengy

David Rickard

Posted on December 11, 2023

Running on Android BOOT_COMPLETED

Hooking into BOOT_COMPLETED is important in Android because that's the only way you can reliably schedule actions to run at certain times. On each boot, all of your actions registered with AlarmManager are cleared away and you need to set them back up again.

However an increasingly large number of Android OEMs block BOOT_COMPLETED by default (Samsung, OnePlus, Huawei, Xiaomi), to prolong battery life on the phone. That is, unless you are lucky enough one of a handful of apps on the default allowlist.

The worst part here is that it's a non-standard permission that the OEMs have stapled onto Android, so there's no standard way to figure out if auto-launch is enabled, and no standard way to request the permissions. There's not even a standard way to open the auto-launch settings activity.

Detection

What can we do? One thing is to be able to detect when BOOT_COMPLETED is being locked. To do this we need a few pieces of information.

1) When was the last time the system booted? SystemClock.elapsedRealtime() provides the answer there. System.currentTimeMillis() - SystemClock.elapsedRealtime() gives you the Unix timestamp in milliseconds.
2) When did the BOOT_COMPLETED receiver last run? You can put this in the receiver:

val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
with(sharedPrefs.edit()) {
    putLong("LastBootReceiverTimestamp", System.currentTimeMillis())
    apply()
}
Enter fullscreen mode Exit fullscreen mode

3) When was the app installed? You can write a similar piece of code to write another timestamp to shared prefs.

You can combine these pieces on app start to figure it out:

val installTimeMs = sharedPrefs.getLong("InstallTimestamp", 0)
if (installTimeMs > 0) {
    val now = System.currentTimeMillis()
    val elapsedSinceBootMs = SystemClock.elapsedRealtime()
    val lastBootTimeMs = now - elapsedSinceBootMs
    val lastBootReceiverTimeMs = sharedPrefs.getLong("LastBootReceiverTimestamp", 0)

    val bootedSinceInstalled = lastBootTimeMs > installTimeMs
    val receiverFiredSinceLastBoot = lastBootReceiverTimeMs > lastBootTimeMs

    // If enough time has passed since device boot that we should have expected the
    // boot receiver to fire, but it hasn't, then we need to ask for auto-launch permission
    if (bootedSinceInstalled
        && !receiverFiredSinceLastBoot
        && elapsedSinceBootMs > Duration.ofMinutes(5).toMillis()) {
        // Auto-launch has been disabled, warn the user
    }
}
Enter fullscreen mode Exit fullscreen mode

Solutions

AutoStarter is a valiant effort, providing an API to help link to the auto-launch settings, that is supposed to encapsulate all the vendor-specific screens that control auto-launch.

It did not work with my OnePlus 8, and even though I found the package and activity I would need to launch, I was denied with a SecurityException. And there are similar crash reports for other devices like Huawei that have locked down the direct opening of this screen. Sadly I don't think it's a good idea to use this, since there doesn't seem to be a great way to work around the security issues or prevent it from throwing exceptions on new phone releases.

I think the only somewhat reliable action you can take is to explain the situation and give a link to open your app's details page, which should hopefully have a way to enable auto-launch there:

val packageName = BuildConfig.APPLICATION_ID
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intent.data = Uri.parse("package:$packageName")
startActivity(intent)
Enter fullscreen mode Exit fullscreen mode

The deeper issue

These phone makers are taking these aggressive measures to contain app activity to prolong battery life. I get where they're coming from, but a number of things really bug me about this:

  • If these measures didn't unreasonably affect app functionality, they wouldn't need to allowlist popular apps.
  • The allowlists further entrench already successful apps. Would you use a competitor to Whatsapp that often fails to tell you when you've gotten a text? They wouldn't get adoption, so they wouldn't get on the whitelist, so they'd never get adoption.
  • Having the OEMs all go their own separate way and not through the official Android app model means it's outright impossible to have a smooth permission-granting experience, and extremely difficult to make even a bumpy one.

This article about CTS-D chronicles some heroics to try to bring the OEMs in line and back to sanity. I think the ideal state here is to have good tools to tell you what's taking your battery, and have battery management handled in a central way by the Android app model. Crossing my fingers here; maybe it will be resolved someday.

💖 💪 🙅 🚩
randomengy
David Rickard

Posted on December 11, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related