PSA: fix Daydreams on Lollipop

    PSA: fix Daydreams on Lollipop

    In which you learn how Lollipop broke Daydreams, and how to deal with it.

    [tl;dr version at the bottom, in the “What we’ve learnt” section]

    Your app has got a really nice Daydream. You’ve spent quite some effort creating it, polishing it, and making everything oh-so-pretty. And you have had that daydream for quite some time now. So much, in fact, that you have almost forgotten you have it.

    At some point (hopefully not after releasing), though, you start getting feedback that says your daydream isn’t working anymore for some users.

    And now we have a problem.

    Things go wrong

    Your users enable Daydreams, and choose your app — yes, yours! — to delight them when their devices are docked, or charging.

    They then actually put their device on the dock, or plug them in to charge, waiting for your daydream to kick in, and what they see is… this:

    Oh hai wrong Daydream

    Errrrrrrrrm… that is not your daydream. So, what did happen there?

    What’s going on

    So by now, you’re wondering what’s going on. That is, besides Android that is seemingly being all like… can't touch this.

    But we’re devs. We fix things™. And, being Android devs, what is the first thing we do when something doesn’t work? That’s right, we look at logcat.

    “Use the filter, Luke!”

    We filter out the log by typing “dream” in the filter text field, and among various Daydream entries, we spot this:

    W/DreamManagerService﹕ Dream ComponentInfo{com.example/com.example.MyDreamService} is not available because its manifest is missing the android.permission.BIND_DREAM_SERVICE permission on the dream service declaration.
    W/DreamManagerService﹕Falling back to default dream ComponentInfo{com.google.android.deskclock/com.android.deskclock.Screensaver}

    So hey, it turns out we are missing a permission in our MyDreamService manifest entry. Android refuses to bind to the daydream service, and instead shows the default daydream, which happens to be the one from the Clock app.

    But this is the first time we see this happening. What the hell, right?

    Let’s investigate.

    Looking into the issue

    If we take a look at the documentation for the BIND_DREAM_SERVICE permission, we see that it’s been introduced in API level 21. Which is Lollipop, in case you weren’t keeping tabs.

    This rings a bell, because recently, you finally have had the time to do this small, but important, change in your build.gradle:

    - targetSdkVersion 19
    + targetSdkVersion 21

    Yay! Lollipop-land, #MaterialYolo, and all those new nice things to play with! And, best of all, it looked like your app still behaved perfectly.

    Or… does it?

    Well, turns out, if you scroll to the very bottom of the Lollipop APIs page, you can find this small nugget of information:

    • BIND_DREAM_SERVICE: When targeting API level 21 and higher, this permission is required by a Daydream service, to ensure that only the system can bind to it.

    So, this is the culprit: we updated our target API level to 21, and things broke because of the behavioural changes this entails.

    This change is actually a good thing for security. Daydreams have to be defined as exported services, otherwise the system won’t be able to bind to them. It’s good practice to protect exported services by requiring a permission, so that only the intended users that possess that permission can bind to them. Of course, you shouldn’t export a service in the first place, unless you have to access them from outside your app. And in that case, you better harden it.

    Before Lollipop, since no such permission was available from the system, daydreams had to be left unguarded, meaning that any other app on the system could bind to them and try and do malicious things, such as stealing informations you display or try to tamper with your app.

    Now that this permission is required, and only system components can hold it, Android is ensuring that only system components can bind to Daydreams, which is obviously a good thing.

    Fixing the problem

    How to fix this issue, then? Simply add this attribute to your Daydream service declaration in AndroidManifest.xml:

    android:permission="android.permission.BIND_DREAM_SERVICE"

    And now you’re all set. When the system will try running your daydream on API 21+ it will find the appropriate permission on the service node and everything will proceed on its merry way.

    This is not going to break things for pre-Lollipop devices, because the system will simply ignore the unknown permission.

    What we’ve learnt

    What have we learnt today?

    • Daydreams must require the BIND_DREAM_SERVICE permission for binding (even if you don’t target API 21+ yet, IMHO)
    • This breaking API change in Lollipop for some reason isn’t listed in the appropriate “Changes” page on Android Developers, unlike the other service binding changes, so always look everywhere for answers
    • Never blindly update the target API version for your app. Always thoroughly test everything when you do.

    Thanks

    I really want to thank my friend and fellow Novodian Daniele Bonaldo for pointing out this issue a few days ago.

    Of course I also thank all the proofreaders of this article, as usual.

    Sebastiano Poggi

    Sebastiano Poggi

    “It depends” 🤷‍♂️ — Google Developer Expert for Android, Flutter and Identity. A geek 🤓 who has a serious thing for good design ✨ and for emojis 🤟working at JetBrains (opinions my own)

    London and elsewhere https://sebastiano.dev