UIKit Apprentice, Second Edition – Now Updated!

Learn iOS and Swift from scratch. Build four powerful apps—with support for iPad and Dark Mode. Publish apps to the App Store.

Home Android & Kotlin Tutorials

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.

5/5 5 Ratings

Version

  • Kotlin 1.4, Android 10.0, Android Studio 4.2

In this tutorial, you’ll learn how to interact with the Android Sleep API and react to the stream of events sent by the system. The Android Sleep API collects information such as surrounding brightness and device movement to make assumptions about when the user is asleep or awake.

This API is handy for tracking users’ sleep patterns to help them improve their sleep habits.

While building a simple sleep tracking app, you’ll how to:

  • Request Activity Recognition permissions for your app.
  • Register a Sleep Receiver to filter and analyze the different events sensed by the device.

With that, it’s time to jump right in! Try not to fall asleep. ;]

Note: This tutorial assumes you have a basic understanding of Android and Kotlin. If you need to catch up on this topic, check out Beginning Android Development with Kotlin.

Getting Started

Click Download Materials at the top or bottom of this tutorial to download the sample project.

Import the starter project in Android Studio. Build and run it. You’ll see a classic sample screen:

Empty Hello World screen

You won’t interact with the UI much in this tutorial, so building and running the app is more about checking the code’s correctness.

Note: You need a physical device with at least Android 10 to use the Sleep API and test the code you’ll write in this tutorial.

Using the Android Sleep API

To use this API, you’ll need two pieces of code:

  1. The ActivityRecognition client that lets you subscribe to sleep updates.
  2. A BrodcastReceiver, to receive the updates from the ActivityRecognition client.

Additionally, you need to ask the user for permission to listen to their sleep data, which they grant outside the app, in the Settings screen.

This API will provide two sets of information:

  • The confidence level that your user is sleeping, which the API reports periodically a few minutes after the previous emission.
  • The daily sleep segment the API emits once the device detects the user is awake.

To use this API, you first need to include the dependencies. Open app ‣ build.gradle and add the following line in the dependencies block:

implementation 'com.google.android.gms:play-services-location:18.0.0'

Now you’re all set to start writing some code.

Listening for Sleep Data

Before you can do anything else, you need to receive sleep data. The component that provides this information is the BroadcastReceiver.

Open SleepReceiver.kt. Notice SleepReceiver already extends BroadcastReceiver with an empty onReceive. This method is where you’ll add the logic that filters the data.

Open AndroidManifest.xml. You’ll see SleepReceiver is already declared inside, right below MainActivity. It looks like this:

<receiver
    android:name=".receiver.SleepReceiver"
    android:enabled="true"
    android:exported="true" />

You need to declare SleepReceiver in AndroidManifest.xml so the system knows BroadcastReceiver is available in your app.

Now that you have SleepReceiver set up, it’s time to write the logic that will filter the events coming from the Intent.

Filtering Sleep Data

Open SleepReceiver. Add the following statement inside onReceive():

if (SleepSegmentEvent.hasEvents(intent)) {
} else if (SleepClassifyEvent.hasEvents(intent)) {
}

Here, you check whether the Intent contains SleepSegmentEvents or SleepClassifyEvents.

Next, add the following companion object to the bottom of the class:

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

You’ll use this tag to log events to the console and filter the text you’ll show in a few lines.

Now, to focus on the first branch of the if statement!

Right below the if line, add:

val events =
    SleepSegmentEvent.extractEvents(intent)

Log.d(TAG, "Logging SleepSegmentEvents")

for (event in events) {
  Log.d(TAG,
      "${event.startTimeMillis} to ${event.endTimeMillis} with status ${event.status}")
}

In case the Intent you receive contains a SleepSegmentEvent, you extract it and print its starting and ending timestamps and its status to the console. This represents the UNIX timestamp when the device detected the user began sleeping, woke up and a status code that indicates if the system succeeded in collecting sleep data, respectively.

Now you can get the sleep segments, but you also want to classify the events. You can do this in the statement’s else branch by adding:

val events = SleepClassifyEvent.extractEvents(intent)

Log.d(TAG, "Logging SleepClassifyEvents")

for (event in events) {
    Log.d(TAG,
        "Confidence: ${event.confidence} - Light: ${event.light} - Motion: ${event.motion}"
    )
}

Once again, you need to extract the information about the event and print its data to the console, highlighting the confidence level of the classification, light quantity and value of the motion.

At this point, if you build and run the app, you won’t see any output in the console. You still need to take a few more steps before you can get the data!

Requesting Activity Recognition Permissions

To get the sleep data, you need to have permission from your user to access it. It’s not as straightforward as with other permissions since you need to open the settings for the data, and the user needs to grant your app the Activity Recognition permissions. But it’s not that complicated either.

First, open AndroidManifest.xml and add the following permission within manifest:

<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />

Next, you need to create a method inside MainActivity.kt that will open the settings screen. Open MainActivity and add the following, importing android.provider.Settings:

private fun requestActivityRecognitionPermission() {

   val intent = Intent().apply {
        action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
        data = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
        flags = Intent.FLAG_ACTIVITY_NEW_TASK
   }

   startActivity(intent)
}

Here you create the Settings Intent, adding the Activity Recognition action and your app package extra. You then run the Intent.

Now, since you need to request permissions, you can leverage the ActivityResultContracts API, which will help reduce the boilerplate to listen for the activity result.

Start by adding the activity ktx library to app ‣ build.gradle:

implementation 'androidx.activity:activity-ktx:1.2.3'

Sync your gradle file. This adds ActivityResultContracts and ActivityResultLauncher to your project.

Then, add this property to MainActivity:

private val permissionRequester: ActivityResultLauncher<String> =
     registerForActivityResult(
         ActivityResultContracts.RequestPermission()
    ) { isGranted ->

       if (!isGranted) {
         requestActivityRecognitionPermission()
       } else {
         // Request goes here
       }
     }

Here, you create an object that will listen for the Activity result from your permission request and pass it to you once this object parses it.

It’s not time to build and run your app yet. If you try, it still won’t show any data. But it will soon!

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.

Where to Go From Here?

Now you know how to interact with the Android Sleep API and receive updates about the user’s sleep segments. You can download the final project using the download button below. To learn more about the Sleep API in the official documentation.

You might want to check more resources to improve the solution you just implemented.

First, check the Android Apprentice Book for a deep and complete understanding of how to create beautiful Android apps.

You could also check our tutorials for specific features you might want to implement, like storing the subscription status with a DataStore or using Room database to store the sleep events.

If you have any questions or comments, please join the forums below!

Average Rating

5/5

Add a rating for this content

5 ratings

More like this

Contributors

Comments