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.)
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:
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.
- Connect your Android device to your computer.
- 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:
On your device, you should see the application:
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.
- Search for the TODO: Review play services library required for activity recognition in the
build.gradle
file for thestart
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'
- In the
start
module, search for TODO: Add activity recognition and boot complete permissions in theAndroidManifest.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