Android Sleep API Codelab

1. Introduction

What you'll build

In this codelab, you're going to build an Android app to detect the user's sleep time. The app will:

  • Request permissions
  • Register for the Android Sleep API
  • Retrieve the API events and display on the UI
  • Unregister when it's no longer needed

What you'll need

  • A recent version of Android Studio with Android Build Tools v21 or higher
  • A device running Android 10 (API level 29) or higher

2. Getting set up

Clone the starter project repo

To get you started as quickly as possible, we have prepared a starter project for you to build on. Check whether you have git by typing git --version in the terminal / command line. If it's not installed, follow these instructions to install it. Then you can simply run the following command to clone the project.

 git clone https://github.com/android/codelab-android-sleep/

Import the project

Start Android Studio, and select "Open an existing Android Studio project" from the Welcome screen and open the project directory.

After the project has loaded, you may also see an alert that Git isn't tracking all your local changes. You can click "Ignore" or the "X" in the upper right. (You won't be pushing any changes back to the Git repo.)

In the upper-left corner of the project window, you should see something like the following image if you are in the Android view. (If you are in the Project view, you will need to expand the project to see the same thing.)

1401a11c10711a60.png

There are two folder icons (start and complete). Each of them are known as a "module."

Please note that Android Studio might take several seconds to compile the project in the background for the first time. During this time you will see a spinner in the status bar at the bottom of Android Studio:

4bc64eb3b99eb0ae.png

We recommend that you wait until this has finished before making code changes. This will allow Android Studio to pull in all the necessary components.

In addition, if you get a prompt saying "Reload for language changes to take effect?" or something similar, select "Yes."

Understand the starter project

All right, you're set up and ready to add activity recognition. We'll be using the start module, which is the starting point for this codelab. In other words, you'll add code from each step to start.

The complete module can be used for checking your work, or for you to reference if you encounter any issues.

Overview of key components:

  • MainActivity.kt: Render the app main screen when the user launches the app.
  • SleepReceiver.kt: Extracts sleep events from the API and stores them to the database.
  • BootReceiver.kt: Re-registers to the Sleep API when device boot is complete, in order to continue listening to Sleep API events.

Run the starter project

Let's run our app.

  1. Connect your Android device to your computer.
  2. In the toolbar, select the start configuration from the drop-down selector, select your device, and then click the green triangle (Run) button next to it:

177045a302bf57d8.png

On your device, you should see the application:

30efe28b9757e1e7.png

311c83ca64556d94.png

We haven't actually added any code to track sleep yet, that will be in the coming sections.

In the next section, we'll review the required library and permissions.

3. Review the library and add permissions

Review build.gradle and AndroidManifest.xml

To use the Sleep API in your app, you must declare a dependency to the Google Location and Activity Recognition API and specify the com.google.android.gms.permission.ACTIVITY_RECOGNITION permission in the app manifest.

  1. Search for the TODO: Review play services library required for activity recognition in the build.gradle file for the start module. There is no action for this step. Just review the declared dependency we require. It should look like this:
    // TODO: Review play services library required for activity recognition.
    implementation 'com.google.android.gms:play-services-location:18.0.0'
  1. In the start module, search for TODO: Add activity recognition and boot complete permissions in the AndroidManifest.xml and add the following code to the <manifest> element.
<!-- TODO: Add activity recognition and receive boot complete permissions. -->
<!-- Required for 29+. -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

Your code should now look something similar to this:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.example.sleepcodelab">
...
<!-- TODO: Add activity recognition and receive boot complete permissions. -->
<!-- Required for 29+. -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  ...
</manifest>

As you can see from the comments, you need to add the runtime permission for API version 29.

That's it! Your app can now support sleep activity recognition. We just need to add the code to get it.

Review Activity Recognition permission check

We need to check and request runtime permissions if needed:

  • In the MainActivity.kt, we will check the activity recognition permissions.
  • If permissions are not granted, we will call displayPermissionSettingsSnackBar(). This snackbar explains the needs for the permission and allows users to approve the permission in system settings.

In the start module, search for TODO: Review Activity Recognition permission check in MainActivity.kt. You should see this code snippet.

Note, there is no action for this section.

// TODO: Review Activity Recognition permission checking.
private fun activityRecognitionPermissionApproved(): Boolean {
   return PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
           this,
           Manifest.permission.ACTIVITY_RECOGNITION
   );
}

Enable/disable Sleep tracking

In the start module, search for TODO: Enable/Disable Sleep tracking and ask for permissions if needed in MainActivity.kt. Replace the onClickRequestSleepData() method with the following code.

// TODO: Enable/Disable sleep tracking and ask for permissions if needed.
fun onClickRequestSleepData(view: View) {
   if (activityRecognitionPermissionApproved()) {
       if (subscribedToSleepData) {
           unsubscribeToSleepSegmentUpdates(applicationContext, sleepPendingIntent)
       } else {
           subscribeToSleepSegmentUpdates(applicationContext, sleepPendingIntent)
       }
   } else {
       requestPermissionLauncher.launch(permission.ACTIVITY_RECOGNITION)
   }
}

If the Activity Recognition permission is approved, and if the user subscribed to sleep data, we subscribe to sleep updates. Otherwise, we unsubscribe.

For the case when the permission is not approved, we send the user to the splash screen activity that explains why we need the permission and allow them to enable it.

4. Subscribe to Sleep API updates

Create a PendingIntent

In the start module, search for TODO: Create a PendingIntent for Sleep API events in MainActivity.kt. Paste the following snippet.

// TODO: Create a PendingIntent for Sleep API events
sleepPendingIntent =
   SleepReceiver.createSleepReceiverPendingIntent(context = applicationContext)

Now we have a way to get updates when the Sleep API data is available.

Request Sleep API updates

In the start module, search for TODO: Request Sleep API updates in MainActivity.kt. Paste the following snippet.

// TODO: Request Sleep API updates
val task = ActivityRecognition.getClient(context).requestSleepSegmentUpdates(
   pendingIntent,
   // Registers for both SleepSegmentEvent and SleepClassifyEvent data.
   SleepSegmentRequest.getDefaultSleepSegmentRequest()
)

task.addOnSuccessListener {
   mainViewModel.updateSubscribedToSleepData(true)
   Log.d(TAG, "Successfully subscribed to sleep data.")
}
task.addOnFailureListener { exception ->
   Log.d(TAG, "Exception when subscribing to sleep data: $exception")
}

After successfully registering for Sleep API updates, your app will receive sleep detection notifications in the registered PendingIntent.

Re-subscribe upon boot complete

In the start module, search for TODO: Request Sleep API upon boot complete in receiver/BootReceiver.kt. Paste the following snippet.

// TODO: Request Sleep API upon boot complete
val subscribedToSleepData = repository.subscribedToSleepDataFlow.first()
if (subscribedToSleepData) {
   subscribeToSleepSegmentUpdates(
       context = context,
       pendingIntent = SleepReceiver.createSleepReceiverPendingIntent(context)
   )
}

The code enables the app to continue receiving Sleep API updates after a device reboot.

5. Processing Events

When a sleep classification or sleep time detection event occurs, your app receives an Intent callback. A list of SleepSegmentEvent or SleepClassifyEvent objects can be extracted from the Intent.

Let's add the code to handle those events.

In the start module, search for TODO: Extract sleep information from PendingIntent in receiver/SleepReceiver.kt. Add the following code after the comment.

// TODO: Extract sleep information from PendingIntent.
if (SleepSegmentEvent.hasEvents(intent)) {
   val sleepSegmentEvents: List<SleepSegmentEvent> =
       SleepSegmentEvent.extractEvents(intent)
   addSleepSegmentEventsToDatabase(repository, sleepSegmentEvents)
} else if (SleepClassifyEvent.hasEvents(intent)) {
   val sleepClassifyEvents: List<SleepClassifyEvent> =
       SleepClassifyEvent.extractEvents(intent)
   addSleepClassifyEventsToDatabase(repository, sleepClassifyEvents)
}

This will save SleepSegmentEvent or SleepClassifyEvent data to a Room database. The MainActivity accesses that database information through a ViewModel using LiveData. To learn more about how those classes work together, check out the Room with a View codelab.

That's it, you are finished! Try running the app.

6. Install the app and review the code

Connect your device with your workstation using a cable. Click "Run" from Android Studio to install and run the app on your device. After granting Activity Recognition Permission and tapping the SUBSCRIBE TO SLEEP DATA button, the first SleepClassifyEvent should show up after 10 minutes or so. SleepSegmentEvent should show up within one day.

Feel free to read through the code in its entirety to review what you have done, and get a better idea of how the code works together.

Optional code reviews

The following code reviews are optional. They provide details about data mapping between the Sleep API and the database entities:

  • data/db/SleepClassifyEventEntity.kt
  • data/db/SleepSegmentEventEntity.kt

Reference docs