Android Sleep API Tutorial: Getting Started

Learn how to use the Android Sleep API in your Kotlin apps to track when the user is asleep, awake, how long they slept, and the confidence of the results. By Roberto Orgiu.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Subscribing to Sleep Data Updates

Next, you’ll subscribe to sleep data updates. To do so, you need to create a new class. Create SleepRequestsManager in the same package as MainActivity. The class declaration should look like:

class SleepRequestsManager(private val context: Context) {

}

This class will manage subscribing and unsubscribing from the update. At first, you’ll only take care of subscribing. To do so, you need to pass in the Context as a constructor parameter.

Next, you’ll add a method that will let you subscribe to the updates for the sleep data. Add this method to your new class:

fun subscribeToSleepUpdates() {

}

Now you have a place to add your subscription.

Open SleepReceiver and scroll down to the TAG constant declaration inside the companion object. Inside the companion object, paste this code which will create a PendingIntent from a Context, passed as a parameter:

fun createPendingIntent(context: Context): PendingIntent {
     val intent = Intent(context, SleepReceiver::class.java)

     return PendingIntent.getBroadcast(
         context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
   }

To subscribe to the updates, you create a PendingIntent from a BroadcastReceiver that will get the updates. You already created that receiver, but you still need the PendingIntent.

At this point, you have everything you need to create the subscription logic. Head back to SleepRequestManager and create the Pending Intent right between the class constructor and the empty subscribeToSleepUpdates():

private val sleepReceiverPendingIntent by lazy {
   SleepReceiver.createPendingIntent(context)
}

This Pending Intent is what you’ll use in the next step to subscribe to updates.

Now, you have everything you need to subscribe to the updates, and you can ask the API to send the data down to your receiver. Inside subscribeToSleepUpdates(), add:

ActivityRecognition.getClient(context)
    .requestSleepSegmentUpdates (sleepReceiverPendingIntent,
        SleepSegmentRequest.getDefaultSleepSegmentRequest())

In this code, you invoke the ActivityRecognition client and request updates about the sleep segments with a default request.

Before you can subscribe, however, you still need to check that the permission is in place. After all, your user can always remove it.

You need to check whether you can access the data and subscribe to the updates if you still can. If you can’t, you need to request permission.

First, add this code above subscribeToSleepUpdates():

fun requestSleepUpdates(requestPermission: () -> Unit = {}) {
   if (ContextCompat.checkSelfPermission(
       context, Manifest.permission.ACTIVITY_RECOGNITION) == 
        PackageManager.PERMISSION_GRANTED) {
     subscribeToSleepUpdates()
   } else {
     requestPermission()
   }
}

Here’s a code breakdown:

  1. The first thing you’ll notice is the requestPermission lambda parameter. This is the callback you need if you don’t have permission to get the data. You get it as a parameter since you need to register the callback inside the Activity anyway, so it’s easier to pass the lambda from outside the method.
  2. In the first line of the method, you use ContextCompat to check if you have the Activity Recognition permissions. If you do, you invoke the subscribeToSleepUpdates() to receive the updates. If not, you request the permission again.

Now, head back to MainActivity. Add a declaration for the SleepRequestManager you just created:

private val sleepRequestManager by lazy{
   SleepRequestsManager(this)
}

Now you have a SleepRequestManager in MainActivity to request sleep updates.

Scroll to the line where you see // Request goes here within permissionRequester, and replace it with a call to the subscription:

sleepRequestManager.subscribeToSleepUpdates()

You invoke the update directly because the user granted your app permission, and you ended up in the else branch. So, in this specific case, you can run the update safely.

Finally, you need to run SleepRequestManager when the user opens the app. Scroll down to the line where you see // Your code at the bottom of onCreate() and add:

sleepRequestManager.requestSleepUpdates(requestPermission = {
     permissionRequester.launch(ACTIVITY_RECOGNITION)
   })

Here, you ask SleepRequestManager to request the updates, and you specify how it should behave if it doesn’t have permission to get the data.

Now, build and run. You’ll notice some events are starting to appear in the Logcat tab! Make sure you accept the permission first.

Sleep receiver events in Logcat

Unsubscribing From Updates

Right now, your app doesn’t stop listening to the sleep update events. However, you might need to unsubscribe to let your users start and stop the app when they wish.

To create the logic to unsubscribe from the updates, open SleepRequestManager and below subscribeToSleepUpdates add:

fun unsubscribeFromSleepUpdates() {
   ActivityRecognition.getClient(context)
       .removeSleepSegmentUpdates(sleepReceiverPendingIntent)
}

Here, you remove the subscription from the ActivityRecognition client.

Now, head back to MainActivity. Override onDestroy() and invoke the unsubcribe method:

override fun onDestroy() {
   super.onDestroy()
   sleepRequestManager.unsubscribeFromSleepUpdates()
}

This code ensures the app stops listening and printing the logs when the user exits.

Now, build and run. Notice it stops printing logs once you exit!

Sleep receiver events in Logcat

Handling Reboots

When the device reboots, the Android system will kill your app, and your software won’t run unless your user reopens it. This is an important consideration because you need to record user sleep patterns when your users are not awake, which is usually at night.

Unfortunately, night is also when most devices update, often followed by a reboot. These reboots could potentially lead to days without data if your app can’t run automatically when a reboot completes.

You can make your app run at two steps in the boot process:

  1. The first, Direct Boot mode, happens after the system reboots but before the user logs into their device.
  2. The other happens after the boot completes and the user unlocks their device.

For this tutorial, you’ll learn how to react to the user logging into their device after a reboot.

Listening to Reboot Events

If you want your app to listen to events after the device reboots, you must add another BroadcastReceiver. Open BootReceiver, and you notice it’s empty and ready for you to fill.

This receiver will run when the device reboots and register for sleep data updates. Right below onReceive() add a companion like this:

companion object {
   private const val TAG = "SLEEP_BOOT_RECEIVER"
}

First, you declare another TAG String constant that you’ll use for the error logs. It’s different from the previous one distinguish it when filtering the output in Logcat.

Now, replace onReceive() with:

override fun onReceive(context: Context, intent: Intent) {
   val sleepRequestManager = SleepRequestsManager(context)

   sleepRequestManager.requestSleepUpdates(requestPermission = {
     Log.d(TAG, "Permission to listen for Sleep Activity has been removed")
   })
 }

Here, you create a new instance of SleepRequestManager and invoke requestSleepUpdate().

This time, you can’t ask the users for permission if they removed it because your app might be running before the user logs in. In that case, they wouldn’t be able to change any settings before unlocking the device. There’s not much you can do to prevent this situation, but you can log the error and perform retention strategies afterward.

To run at boot safely, you need to register your receiver in AndroidManifest.xml with some special flags that will let the system know it’s ok for your app to run as soon as the device boots. This is already in AndroidManifest.xml for you:

<receiver
    android:name=".receiver.BootReceiver"
    android:enabled="true"
    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.QUICKBOOT_POWERON" />

    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</receiver>

Note that you need to add the RECEIVE_BOOT_COMPLETED permission to the Receiver declaration. Otherwise, the Android system won’t notify your app when the boot completes, and it won’t run automatically.

Build and run. Congratulations! Now you can successfully track sleep data.