Detect Users’ Activity in Android Using the Transition API

In today’s world, mobile devices are an essential part of our daily lives—we use them as we walk, run, drive, and when doing many other activities.

Understanding what users are doing when holding their phones can allow your apps to adapt intuitively based on those user actions. And for some apps, it might be a requirement to identify when a user starts or stops a particular activity. For example, an app can start music in the background if a user starts running, or it can calculate the number of calories burned.

In today’s article, we’ll learn how to detect and recognize use activities in Android—that way, your app will be smarter about user interaction and will be able to deliver a highly-customized experience.

To do this, we’ll be using an API called Activity Recognition Transition API. This library is built upon device sensors available (gyroscope, accelerometer, etc.) to detect changes in the user’s activity with high accuracy. Additionally, and perhaps most importantly, it consumes less power.

Project Setup 📐

Start by creating a new project in Android Studio, or just open an existing project in which you want to add the activity recognition feature.

The first thing to configure is the dependencies. Add the following lines in the app’s build.gradle file:

dependencies {
    //.... other dependencies
    
    implementation "com.google.android.gms:play-services-location:17.0.0"
    implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
}

Next, add the required permission in the AndroidManifest.xml :

<!-- Required for 28 and below -->
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />

<!-- Required for 29+. -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />

Activity Types 🏃

The Transition API gives you the ability to detect many users activity types— here’s the list

  • IN_VEHICLE : Indicates the device is in a vehicle.
  • ON_BICYCLE : Indicates the device is on a bicycle.
  • RUNNING : Indicates the device is with a user who is running.
  • ON_FOOT : Indicates the device is with a user who is walking or running.
  • STILL : Indicates the device is not moving.
  • WALKING : Indicates the devices is with a user who is walking.

The API also lets you know if the specified activity is just about to start or is ending. This is represented by a transition type:

  • ACTIVITY_TRANSITION_ENTER : The user entering the specified activity.
  • ACTIVITY_TRANSITION_EXIT: The user exiting the specified activity.

We’ll need to know both the type and the activity name we’re looking for as we work through our implementation in the following sections.

Creating an API Request 🔧

In order to tell the API which activities we want to monitor, we need to know both the Activity name and the transition type beforehand. Then, we create a list of ActivityTransition objects, and from this list, we create anActivityTransitionRequest object to form the request.

Here’s a look at how to achieve that:

val transitionsList = mutableListOf<ActivityTransition>()

transitionsList.add(
      ActivityTransition.Builder()
          .setActivityType(DetectedActivity.IN_VEHICLE)
          .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
          .build()
)

transitionsList.add(
      ActivityTransition.Builder()
          .setActivityType(DetectedActivity.IN_VEHICLE)
          .setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
          .build()
)

val finalRequest = ActivityTransitionRequest(transitions)

Receiving Results 📥

Before moving forward, we need to set up a place for receiving updates from the API. For instance, an update can be that the user enters the IN_VEHICLE activity.

We can do that by using the BroadcastReceiver class. We create the class we need by extending the BroadcastReceiver class, then we override the onReceive() method to receive updates from the API.

In the onReceive() method, we perform the required action after detecting the activity—this can be sending a notification to the user, or starting a background service—it’s really up to you to handle what happens after detecting the specified activity.

Here is an implementation of our BroadcastReceiver:

import android.content.BroadcastReceiver
import com.google.android.gms.location.*

class DetectedActivityReceiver : BroadcastReceiver() {
  
    private val RECEIVER_ACTION = BuildConfig.APPLICATION_ID + ".DetectedActivityReceiver"
  
    override fun onReceive(context: Context?, intent: Intent) {
          
          if (RECEIVER_ACTION == intent.action) {
              Log.d("DetectedActivityReceiver", "Received an unsupported action.")
              return
          }
           
          if (ActivityTransitionResult.hasResult(intent)) {
              val result = ActivityTransitionResult.extractResult(intent)
              for (event in result!!.transitionEvents) {
                  val activity = activityType(event.activityType).toString()
                  val transition = transitionType(event.transitionType).toString()
                  val message = "Transition: $activity ($transition)"
                  Log.d("DetectedActivityReceiver", message)
              }
          }
    }
  
    private fun transitionType(transitionType: Int): String? {
        return when (transitionType) {
            ActivityTransition.ACTIVITY_TRANSITION_ENTER -> "ENTER"
            ActivityTransition.ACTIVITY_TRANSITION_EXIT -> "EXIT"
            else -> "UNKNOWN"
        }
    }

    private fun activityType(activity: Int): String? {
        return when (activity) {
            DetectedActivity.IN_VEHICLE -> "IN_VEHICLE"
            DetectedActivity.STILL -> "STILL"
            DetectedActivity.WALKING -> "WALKING"
            else -> "UNKNOWN"
        }
    }
}

Connecting the Component 🔗

Now that we have a receiver to handle the updates from the API, we can tell the API where to send the updates. Here are the steps for that:

  • First, we create an intent with the RECEIVER_ACTION action constant we defined in our receiver.
  • Then we create a PendingIntent from a broadcast receiver with the previous intent.
val intent = Intent(DetectedActivityReceiver.RECEIVER_ACTION)
pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)
receiver = DetectedActivityReceiver()

LocalBroadcastManager.getInstance(this).registerReceiver(
    receiver, IntentFilter(RECEIVER_ACTION)
)

Let me explain this step a bit further. We know that a BroadcastReceiver can receive and respond to broadcast messages from other applications or from the system. But to do so, this receiver needs to be registered first, and we did that ion the above code using the LocalBroadcastManager.

When we register a broadcast receiver, we need to specify also what to respond for—in other words, what events to listen and to respond to. We do that using an IntentFilter with our custom action RECEIVER_ACTION.

val task = ActivityRecognition.getClient(this)
    .requestActivityTransitionUpdates(finalRequest, pendingIntent)

task.addOnSuccessListener {
    Log.d("ActivityRecognition", "Transitions Api registered with success")
}

task.addOnFailureListener { e: Exception ->
    Log.d("ActivityRecognition", "Transitions Api could NOT be registered ${e.localizedMessage}")
}

Here’s a full code snippet for the previous steps:

// creating the pending intent
val intent = Intent(DetectedActivityReceiver.RECEIVER_ACTION)
pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)

// creating the receiver
receiver = DetectedActivityReceiver()

// registring the receiver
LocalBroadcastManager.getInstance(this).registerReceiver(
    receiver, IntentFilter(DetectedActivityReceiverRECEIVER_ACTION)
)

val task = ActivityRecognition.getClient(this)
    .requestActivityTransitionUpdates(finalRequest, pendingIntent)

task.addOnSuccessListener {
    Log.d("ActivityRecognition", "Transitions Api registered with success")
}

task.addOnFailureListener { e: Exception ->
    Log.d("ActivityRecognition", "Transitions Api could NOT be registered ${e.localizedMessage}")
}

Stop Tracking 🛑

At some point in time, if you want to cancel and stop updates from the API, you can do that with the onStop() or onDestroy() methods, or in any place you think it’s necessary to stop the tracking.

We have to do two things to stop the recognition activity:

  • Unregister the receiver to stop receiving incoming data.
  • Tell the API to simply remove the updates from our receiver.

Here’s a method—stopActivityRecogntion()—you can define and then call in when necessary:

private fun stopActivityRecogntion() {
    
    LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
    
    ActivityRecognition.getClient(this)
        .removeActivityTransitionUpdates(pendingIntent)
        .addOnSuccessListener {
            Log.d("ActivityRecognition", "ActivityTransitions successfully unregistered.")
        }
        .addOnFailureListener { e ->
            Log.d("ActivityRecognition", "ActivityTransitions could not be unregistered: $e")
        }
}

Conclusion 🏁

We saw in this article how you can detect a user’s activity and behave accordingly, depending on what you want your app to do. You can use this powerful API to provide your users with a highly-customizable and immersive experience.

I hope you enjoyed this Android article, if you have any questions don’t hesitate to post it or contact me on Twitter, LinkedIn, GitHub, and Facebook.

Don’t forget to clap 👏 and follow 📌if you like what you read, here are other articles you can read also.

Fritz

Our team has been at the forefront of Artificial Intelligence and Machine Learning research for more than 15 years and we're using our collective intelligence to help others learn, understand and grow using these new technologies in ethical and sustainable ways.

Comments 0 Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

wix banner square